Bug 1203246 - Factor track negotiation stuff out of JsepSessionImpl, and other simplification. r=mt
authorByron Campen [:bwc] <docfaraday@gmail.com>
Tue, 25 Aug 2015 08:16:38 -0500
changeset 297086 6e189be469ed5b54074da69594ff5383ff49da64
parent 297085 15e860fa81e36d399c7160623ac37f53ba5ee363
child 297087 72fde1ca508a240eba6afa614ef3d7edefbd6142
push id962
push userjlund@mozilla.com
push dateFri, 04 Dec 2015 23:28:54 +0000
treeherdermozilla-release@23a2d286e80f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmt
bugs1203246
milestone43.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 1203246 - Factor track negotiation stuff out of JsepSessionImpl, and other simplification. r=mt
media/webrtc/signaling/signaling.gyp
media/webrtc/signaling/src/jsep/JsepCodecDescription.h
media/webrtc/signaling/src/jsep/JsepSession.h
media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
media/webrtc/signaling/src/jsep/JsepSessionImpl.h
media/webrtc/signaling/src/jsep/JsepTrack.cpp
media/webrtc/signaling/src/jsep/JsepTrack.h
media/webrtc/signaling/src/jsep/JsepTrackImpl.h
media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
media/webrtc/signaling/src/sdp/SdpAttribute.cpp
media/webrtc/signaling/src/sdp/SdpAttribute.h
media/webrtc/signaling/src/sdp/SdpEnum.h
media/webrtc/signaling/src/sdp/SdpHelper.cpp
media/webrtc/signaling/src/sdp/SdpHelper.h
media/webrtc/signaling/src/sdp/SdpMediaSection.cpp
media/webrtc/signaling/src/sdp/SdpMediaSection.h
media/webrtc/signaling/src/sdp/SipccSdpMediaSection.cpp
media/webrtc/signaling/src/sdp/SipccSdpMediaSection.h
media/webrtc/signaling/test/jsep_session_unittest.cpp
--- a/media/webrtc/signaling/signaling.gyp
+++ b/media/webrtc/signaling/signaling.gyp
@@ -154,30 +154,32 @@
          './src/sdp/Sdp.h',
          './src/sdp/SdpAttribute.h',
          './src/sdp/SdpAttribute.cpp',
          './src/sdp/SdpAttributeList.h',
          './src/sdp/SdpErrorHolder.h',
          './src/sdp/SdpHelper.h',
          './src/sdp/SdpHelper.cpp',
          './src/sdp/SdpMediaSection.h',
+         './src/sdp/SdpMediaSection.cpp',
          './src/sdp/SipccSdp.h',
          './src/sdp/SipccSdpAttributeList.h',
          './src/sdp/SipccSdpAttributeList.cpp',
          './src/sdp/SipccSdpMediaSection.h',
          './src/sdp/SipccSdpParser.h',
          './src/sdp/SipccSdp.cpp',
          './src/sdp/SipccSdpMediaSection.cpp',
          './src/sdp/SipccSdpParser.cpp',
 
          # JSEP
          './src/jsep/JsepCodecDescription.h',
          './src/jsep/JsepSession.h',
          './src/jsep/JsepSessionImpl.cpp',
          './src/jsep/JsepSessionImpl.h',
+         './src/jsep/JsepTrack.cpp',
          './src/jsep/JsepTrack.h',
          './src/jsep/JsepTransport.h'
       ],
 
       #
       # DEFINES
       #
 
--- a/media/webrtc/signaling/src/jsep/JsepCodecDescription.h
+++ b/media/webrtc/signaling/src/jsep/JsepCodecDescription.h
@@ -1,52 +1,50 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef _JSEPCODECDESCRIPTION_H_
 #define _JSEPCODECDESCRIPTION_H_
 
-#include <iostream>
 #include <string>
 #include "signaling/src/sdp/SdpMediaSection.h"
 #include "signaling/src/sdp/SdpHelper.h"
 #include "nsCRT.h"
 
 namespace mozilla {
 
 #define JSEP_CODEC_CLONE(T)                                                    \
   virtual JsepCodecDescription* Clone() const override                         \
   {                                                                            \
     return new T(*this);                                                       \
   }
 
 // A single entry in our list of known codecs.
-struct JsepCodecDescription {
+class JsepCodecDescription {
+ public:
   JsepCodecDescription(mozilla::SdpMediaSection::MediaType type,
                        const std::string& defaultPt,
                        const std::string& name,
                        uint32_t clock,
                        uint32_t channels,
                        bool enabled)
       : mType(type),
         mDefaultPt(defaultPt),
         mName(name),
         mClock(clock),
         mChannels(channels),
         mEnabled(enabled),
         mStronglyPreferred(false),
-        mNegotiated(false)
+        mDirection(sdp::kSend)
   {
   }
   virtual ~JsepCodecDescription() {}
 
   virtual JsepCodecDescription* Clone() const = 0;
-  virtual void AddFmtps(SdpFmtpAttributeList& fmtp) const = 0;
-  virtual void AddRtcpFbs(SdpRtcpFbAttributeList& rtcpfb) const = 0;
 
   bool
   GetPtAsInt(uint16_t* ptOutparam) const
   {
     return SdpHelper::GetPtAsInt(mDefaultPt, ptOutparam);
   }
 
   virtual bool
@@ -70,133 +68,76 @@ struct JsepCodecDescription {
   virtual bool
   ParametersMatch(const std::string& fmt,
                   const SdpMediaSection& remoteMsection) const
   {
     return true;
   }
 
   virtual bool
-  Negotiate(const SdpMediaSection& remoteMsection)
+  Negotiate(const std::string& pt, const SdpMediaSection& remoteMsection)
   {
-    mNegotiated = true;
-    return true;
-  }
-
-  virtual bool LoadSendParameters(
-      const mozilla::SdpMediaSection& remoteMsection)
-  {
-    return true;
-  }
-
-  virtual bool LoadRecvParameters(
-      const mozilla::SdpMediaSection& remoteMsection)
-  {
+    mDefaultPt = pt;
     return true;
   }
 
   virtual void
   AddToMediaSection(SdpMediaSection& msection) const
   {
     if (mEnabled && msection.GetMediaType() == mType) {
-      if (mType == SdpMediaSection::kApplication) {
-        // Hack: using mChannels for number of streams
-        msection.AddDataChannel(mDefaultPt, mName, mChannels);
-      } else {
-        msection.AddCodec(mDefaultPt, mName, mClock, mChannels);
+      // Both send and recv codec will have the same pt, so don't add twice
+      if (!msection.HasFormat(mDefaultPt)) {
+        if (mType == SdpMediaSection::kApplication) {
+          // Hack: using mChannels for number of streams
+          msection.AddDataChannel(mDefaultPt, mName, mChannels);
+        } else {
+          msection.AddCodec(mDefaultPt, mName, mClock, mChannels);
+        }
       }
-      AddFmtpsToMSection(msection);
-      AddRtcpFbsToMSection(msection);
+
+      AddParametersToMSection(msection);
     }
   }
 
-  virtual void
-  AddFmtpsToMSection(SdpMediaSection& msection) const
-  {
-    SdpAttributeList& attrs = msection.GetAttributeList();
-
-    UniquePtr<SdpFmtpAttributeList> fmtps;
-
-    if (attrs.HasAttribute(SdpAttribute::kFmtpAttribute)) {
-      fmtps.reset(new SdpFmtpAttributeList(attrs.GetFmtp()));
-    } else {
-      fmtps.reset(new SdpFmtpAttributeList);
-    }
-
-    AddFmtps(*fmtps);
-
-    if (!fmtps->mFmtps.empty()) {
-      attrs.SetAttribute(fmtps.release());
-    }
-  }
-
-  virtual void
-  AddRtcpFbsToMSection(SdpMediaSection& msection) const
-  {
-    SdpAttributeList& attrs = msection.GetAttributeList();
-
-    UniquePtr<SdpRtcpFbAttributeList> rtcpfbs;
-
-    if (attrs.HasAttribute(SdpAttribute::kRtcpFbAttribute)) {
-      rtcpfbs.reset(new SdpRtcpFbAttributeList(attrs.GetRtcpFb()));
-    } else {
-      rtcpfbs.reset(new SdpRtcpFbAttributeList);
-    }
-
-    AddRtcpFbs(*rtcpfbs);
-
-    if (!rtcpfbs->mFeedbacks.empty()) {
-      attrs.SetAttribute(rtcpfbs.release());
-    }
-  }
+  virtual void AddParametersToMSection(SdpMediaSection& msection) const {}
 
   mozilla::SdpMediaSection::MediaType mType;
   std::string mDefaultPt;
   std::string mName;
   uint32_t mClock;
   uint32_t mChannels;
   bool mEnabled;
   bool mStronglyPreferred;
-  bool mNegotiated;
+  sdp::Direction mDirection;
 };
 
-struct JsepAudioCodecDescription : public JsepCodecDescription {
+class JsepAudioCodecDescription : public JsepCodecDescription {
+ public:
   JsepAudioCodecDescription(const std::string& defaultPt,
                             const std::string& name,
                             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)
   {
   }
 
-  virtual void
-  AddFmtps(SdpFmtpAttributeList& fmtp) const override
-  {
-    // TODO
-  }
-
-  virtual void
-  AddRtcpFbs(SdpRtcpFbAttributeList& rtcpfb) const override
-  {
-    // TODO: Do we want to add anything?
-  }
-
   JSEP_CODEC_CLONE(JsepAudioCodecDescription)
 
   uint32_t mPacketSize;
   uint32_t mBitrate;
 };
 
-struct JsepVideoCodecDescription : public JsepCodecDescription {
+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),
         mMaxFs(0),
         mMaxFr(0),
@@ -212,62 +153,93 @@ struct JsepVideoCodecDescription : publi
     mCcmFbTypes.push_back(SdpRtcpFbAttributeList::fir);
   }
 
   virtual void
   EnableTmmbr() {
     mCcmFbTypes.push_back(SdpRtcpFbAttributeList::tmmbr);
   }
 
-  virtual void
-  AddFmtps(SdpFmtpAttributeList& fmtp) const override
+  void
+  AddParametersToMSection(SdpMediaSection& msection) const override
+  {
+    AddFmtpsToMSection(msection);
+    AddRtcpFbsToMSection(msection);
+  }
+
+  void
+  AddFmtpsToMSection(SdpMediaSection& msection) const
   {
     if (mName == "H264") {
-      UniquePtr<SdpFmtpAttributeList::H264Parameters> params =
-          MakeUnique<SdpFmtpAttributeList::H264Parameters>();
+      SdpFmtpAttributeList::H264Parameters h264Params(
+          GetH264Parameters(mDefaultPt, msection));
 
-      params->packetization_mode = mPacketizationMode;
+      if (mDirection == sdp::kSend) {
+        if (!h264Params.level_asymmetry_allowed) {
+          // First time the fmtp has been set; set just in case this is for a
+          // sendonly m-line, since even though we aren't receiving the level
+          // negotiation still needs to happen (sigh).
+          h264Params.profile_level_id = mProfileLevelId;
+        }
+      } else {
+        // Parameters that only apply to what we receive
+        h264Params.max_mbps = mMaxMbps;
+        h264Params.max_fs = mMaxFs;
+        h264Params.max_cpb = mMaxCpb;
+        h264Params.max_dpb = mMaxDpb;
+        h264Params.max_br = mMaxBr;
+        strncpy(h264Params.sprop_parameter_sets,
+                mSpropParameterSets.c_str(),
+                sizeof(h264Params.sprop_parameter_sets) - 1);
+        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?
-      params->level_asymmetry_allowed = true;
-      params->profile_level_id = mProfileLevelId;
-      params->max_mbps = mMaxMbps;
-      params->max_fs = mMaxFs;
-      params->max_cpb = mMaxCpb;
-      params->max_dpb = mMaxDpb;
-      params->max_br = mMaxBr;
-      strncpy(params->sprop_parameter_sets,
-              mSpropParameterSets.c_str(),
-              sizeof(params->sprop_parameter_sets) - 1);
-      fmtp.PushEntry(mDefaultPt, "", mozilla::Move(params));
+      h264Params.level_asymmetry_allowed = true;
+
+      msection.SetFmtp(
+          SdpFmtpAttributeList::Fmtp(mDefaultPt, "", h264Params));
     } else if (mName == "VP8" || mName == "VP9") {
-      // VP8 and VP9 share the same SDP parameters thus far
-      UniquePtr<SdpFmtpAttributeList::VP8Parameters> params =
-          MakeUnique<SdpFmtpAttributeList::VP8Parameters>(
-              mName == "VP8" ?
-              SdpRtpmapAttributeList::kVP8 :
-              SdpRtpmapAttributeList::kVP9);
+      if (mDirection == sdp::kRecv) {
+        // VP8 and VP9 share the same SDP parameters thus far
+        SdpFmtpAttributeList::VP8Parameters vp8Params(
+            GetVP8Parameters(mDefaultPt, msection));
 
-      params->max_fs = mMaxFs;
-      params->max_fr = mMaxFr;
-      fmtp.PushEntry(mDefaultPt, "", mozilla::Move(params));
+        vp8Params.max_fs = mMaxFs;
+        vp8Params.max_fr = mMaxFr;
+        msection.SetFmtp(
+            SdpFmtpAttributeList::Fmtp(mDefaultPt, "", vp8Params));
+      }
     }
   }
 
-  virtual void
-  AddRtcpFbs(SdpRtcpFbAttributeList& rtcpfb) const override
+  void
+  AddRtcpFbsToMSection(SdpMediaSection& msection) const
   {
+    SdpRtcpFbAttributeList rtcpfbs(msection.GetRtcpFbs());
+    for (const auto& rtcpfb : rtcpfbs.mFeedbacks) {
+      if (rtcpfb.pt == mDefaultPt) {
+        // Already set by the codec for the other direction.
+        return;
+      }
+    }
+
     for (const std::string& type : mAckFbTypes) {
-      rtcpfb.PushEntry(mDefaultPt, SdpRtcpFbAttributeList::kAck, type);
+      rtcpfbs.PushEntry(mDefaultPt, SdpRtcpFbAttributeList::kAck, type);
     }
     for (const std::string& type : mNackFbTypes) {
-      rtcpfb.PushEntry(mDefaultPt, SdpRtcpFbAttributeList::kNack, type);
+      rtcpfbs.PushEntry(mDefaultPt, SdpRtcpFbAttributeList::kNack, type);
     }
     for (const std::string& type : mCcmFbTypes) {
-      rtcpfb.PushEntry(mDefaultPt, SdpRtcpFbAttributeList::kCcm, type);
+      rtcpfbs.PushEntry(mDefaultPt, SdpRtcpFbAttributeList::kCcm, type);
     }
+
+    msection.SetRtcpFbs(rtcpfbs);
   }
 
   SdpFmtpAttributeList::H264Parameters
   GetH264Parameters(const std::string& pt,
                     const SdpMediaSection& msection) const
   {
     // Will contain defaults if nothing else
     SdpFmtpAttributeList::H264Parameters result;
@@ -321,32 +293,60 @@ struct JsepVideoCodecDescription : publi
   {
     // 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);
   }
 
   virtual bool
-  Negotiate(const SdpMediaSection& remoteMsection) override
+  Negotiate(const std::string& pt,
+            const SdpMediaSection& remoteMsection) override
   {
+    JsepCodecDescription::Negotiate(pt, remoteMsection);
     if (mName == "H264") {
       SdpFmtpAttributeList::H264Parameters h264Params(
           GetH264Parameters(mDefaultPt, remoteMsection));
+
+      // Level is negotiated symmetrically if level asymmetry is disallowed
       if (!h264Params.level_asymmetry_allowed) {
         SetSaneH264Level(std::min(GetSaneH264Level(h264Params.profile_level_id),
                                   GetSaneH264Level(mProfileLevelId)),
                          &mProfileLevelId);
       }
 
-      // TODO(bug 1143709): max-recv-level support
+      if (mDirection == sdp::kSend) {
+        // Remote values of these apply only to the send codec.
+        mMaxFs = h264Params.max_fs;
+        mMaxMbps = h264Params.max_mbps;
+        mMaxCpb = h264Params.max_cpb;
+        mMaxDpb = h264Params.max_dpb;
+        mMaxBr = h264Params.max_br;
+        mSpropParameterSets = h264Params.sprop_parameter_sets;
+        // 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 == "VP8" || mName == "VP9") {
+      if (mDirection == sdp::kSend) {
+        SdpFmtpAttributeList::VP8Parameters vp8Params(
+            GetVP8Parameters(mDefaultPt, remoteMsection));
+
+        mMaxFs = vp8Params.max_fs;
+        mMaxFr = vp8Params.max_fr;
+      }
     }
 
     NegotiateRtcpFb(remoteMsection);
-    return JsepCodecDescription::Negotiate(remoteMsection);
+    return true;
   }
 
   // Maps the not-so-sane encoding of H264 level into something that is
   // ordered in the way one would expect
   // 1b is 0xAB, everything else is the level left-shifted one half-byte
   // (eg; 1.0 is 0xA0, 1.1 is 0xB0, 3.1 is 0x1F0)
   static uint32_t
   GetSaneH264Level(uint32_t profileLevelId)
@@ -391,57 +391,16 @@ struct JsepVideoCodecDescription : publi
     } else {
       // Not 1b, just shift
       level = level >> 4;
     }
 
     *profileLevelId = (*profileLevelId & ~levelMask) | level;
   }
 
-  virtual bool
-  LoadSendParameters(const mozilla::SdpMediaSection& remoteMsection) override
-  {
-
-    if (mName == "H264") {
-      SdpFmtpAttributeList::H264Parameters h264Params(
-          GetH264Parameters(mDefaultPt, remoteMsection));
-
-      if (!h264Params.level_asymmetry_allowed) {
-        SetSaneH264Level(std::min(GetSaneH264Level(h264Params.profile_level_id),
-                                  GetSaneH264Level(mProfileLevelId)),
-                         &mProfileLevelId);
-      } else {
-        SetSaneH264Level(GetSaneH264Level(h264Params.profile_level_id),
-                         &mProfileLevelId);
-      }
-
-      mMaxFs = h264Params.max_fs;
-      mMaxMbps = h264Params.max_mbps;
-      mMaxCpb = h264Params.max_cpb;
-      mMaxDpb = h264Params.max_dpb;
-      mMaxBr = h264Params.max_br;
-      mSpropParameterSets = h264Params.sprop_parameter_sets;
-    } else if (mName == "VP8" || mName == "VP9") {
-      SdpFmtpAttributeList::VP8Parameters vp8Params(
-          GetVP8Parameters(mDefaultPt, remoteMsection));
-
-      mMaxFs = vp8Params.max_fs;
-      mMaxFr = vp8Params.max_fr;
-    }
-
-    NegotiateRtcpFb(remoteMsection);
-    return true;
-  }
-
-  virtual bool
-  LoadRecvParameters(const mozilla::SdpMediaSection& remoteMsection) override
-  {
-    return Negotiate(remoteMsection);
-  }
-
   enum Subprofile {
     kH264ConstrainedBaseline,
     kH264Baseline,
     kH264Main,
     kH264Extended,
     kH264High,
     kH264High10,
     kH264High42,
@@ -595,38 +554,27 @@ struct JsepVideoCodecDescription : publi
   uint32_t mPacketizationMode;
   uint32_t mMaxMbps;
   uint32_t mMaxCpb;
   uint32_t mMaxDpb;
   uint32_t mMaxBr;
   std::string mSpropParameterSets;
 };
 
-struct JsepApplicationCodecDescription : public JsepCodecDescription {
+class JsepApplicationCodecDescription : public JsepCodecDescription {
+ public:
   JsepApplicationCodecDescription(const std::string& defaultPt,
                                   const std::string& name,
                                   uint16_t channels,
                                   bool enabled = true)
       : JsepCodecDescription(mozilla::SdpMediaSection::kApplication, defaultPt,
                              name, 0, channels, enabled)
   {
   }
 
-  virtual void
-  AddFmtps(SdpFmtpAttributeList& fmtp) const override
-  {
-    // TODO: Is there anything to do here?
-  }
-
-  virtual void
-  AddRtcpFbs(SdpRtcpFbAttributeList& rtcpfb) const override
-  {
-    // Nothing to do here.
-  }
-
   JSEP_CODEC_CLONE(JsepApplicationCodecDescription)
 
   // Override, uses sctpmap instead of rtpmap
   virtual bool
   Matches(const std::string& fmt,
           const SdpMediaSection& remoteMsection) const override
   {
     if (mType != remoteMsection.GetMediaType()) {
--- a/media/webrtc/signaling/src/jsep/JsepSession.h
+++ b/media/webrtc/signaling/src/jsep/JsepSession.h
@@ -14,17 +14,17 @@
 
 #include "signaling/src/jsep/JsepTransport.h"
 #include "signaling/src/sdp/Sdp.h"
 
 
 namespace mozilla {
 
 // Forward declarations
-struct JsepCodecDescription;
+class JsepCodecDescription;
 class JsepTrack;
 struct JsepTrackPair;
 
 enum JsepSignalingState {
   kJsepStateStable,
   kJsepStateHaveLocalOffer,
   kJsepStateHaveRemoteOffer,
   kJsepStateHaveLocalPranswer,
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
@@ -14,17 +14,16 @@
 #include "pk11pub.h"
 #include "nsDebug.h"
 
 #include <mozilla/Move.h>
 #include <mozilla/UniquePtr.h>
 
 #include "signaling/src/jsep/JsepTrack.h"
 #include "signaling/src/jsep/JsepTrack.h"
-#include "signaling/src/jsep/JsepTrackImpl.h"
 #include "signaling/src/jsep/JsepTransport.h"
 #include "signaling/src/sdp/Sdp.h"
 #include "signaling/src/sdp/SipccSdp.h"
 #include "signaling/src/sdp/SipccSdpParser.h"
 #include "mozilla/net/DataChannelProtocol.h"
 
 namespace mozilla {
 
@@ -33,25 +32,16 @@ MOZ_MTLOG_MODULE("jsep")
 #define JSEP_SET_ERROR(error)                                                  \
   do {                                                                         \
     std::ostringstream os;                                                     \
     os << error;                                                               \
     mLastError = os.str();                                                     \
     MOZ_MTLOG(ML_ERROR, mLastError);                                           \
   } while (0);
 
-JsepSessionImpl::~JsepSessionImpl()
-{
-  for (auto& codecs : mCodecsByLevel) {
-    for (JsepCodecDescription* codec : codecs) {
-      delete codec;
-    }
-  }
-}
-
 nsresult
 JsepSessionImpl::Init()
 {
   mLastError.clear();
 
   MOZ_ASSERT(!mSessionId, "Init called more than once");
 
   nsresult rv = SetupIds();
@@ -108,32 +98,33 @@ FindUnassignedTrackByType(std::vector<T>
 
   return tracks.end();
 }
 
 nsresult
 JsepSessionImpl::AddTrack(const RefPtr<JsepTrack>& track)
 {
   mLastError.clear();
-  MOZ_ASSERT(track->GetDirection() == JsepTrack::kJsepTrackSending);
+  MOZ_ASSERT(track->GetDirection() == sdp::kSend);
 
   if (track->GetMediaType() != SdpMediaSection::kApplication) {
     track->SetCNAME(mCNAME);
 
     if (track->GetSsrcs().empty()) {
       uint32_t ssrc;
       nsresult rv = CreateSsrc(&ssrc);
       NS_ENSURE_SUCCESS(rv, rv);
       track->AddSsrc(ssrc);
     }
   }
 
+  track->PopulateCodecs(mSupportedCodecs.values);
+
   JsepSendingTrack strack;
   strack.mTrack = track;
-  strack.mNegotiated = false;
 
   mLocalTracks.push_back(strack);
 
   return NS_OK;
 }
 
 nsresult
 JsepSessionImpl::RemoveTrack(const std::string& streamId,
@@ -349,17 +340,17 @@ JsepSessionImpl::SetupOfferMSectionsByTy
   }
 
   // Make sure every local track has an m-section
   nsresult rv = BindLocalTracks(mediatype, sdp);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Make sure that m-sections that previously had a remote track have the
   // recv bit set. Only matters for renegotiation.
-  rv = EnsureRecvForRemoteTracks(mediatype, sdp, offerToReceiveCountPtr);
+  rv = BindRemoteTracks(mediatype, sdp, offerToReceiveCountPtr);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // If we need more recv sections, start setting the recv bit on other
   // msections. If not, disable msections that have no tracks.
   rv = SetRecvAsNeededOrDisable(mediatype,
                                 sdp,
                                 offerToReceiveCountPtr);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -369,83 +360,64 @@ JsepSessionImpl::SetupOfferMSectionsByTy
     rv = AddRecvonlyMsections(mediatype, *offerToReceiveCountPtr, sdp);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
 
 nsresult
-JsepSessionImpl::BindLocalTracks(SdpMediaSection::MediaType mediatype,
-                                 Sdp* sdp)
+JsepSessionImpl::BindLocalTracks(SdpMediaSection::MediaType mediatype, Sdp* sdp)
 {
-  for (auto track = mLocalTracks.begin(); track != mLocalTracks.end();
-       ++track) {
-    if (mediatype != track->mTrack->GetMediaType()) {
+  for (JsepSendingTrack& track : mLocalTracks) {
+    if (mediatype != track.mTrack->GetMediaType()) {
       continue;
     }
 
     SdpMediaSection* msection;
-
-    if (track->mAssignedMLine.isSome()) {
-      // Renegotiation
-      msection = &sdp->GetMediaSection(*track->mAssignedMLine);
+    if (track.mAssignedMLine.isSome()) {
+      msection = &sdp->GetMediaSection(*track.mAssignedMLine);
     } else {
-      nsresult rv = GetFreeMsectionForSend(track->mTrack->GetMediaType(),
+      nsresult rv = GetFreeMsectionForSend(track.mTrack->GetMediaType(),
                                            sdp,
                                            &msection);
       NS_ENSURE_SUCCESS(rv, rv);
+      track.mAssignedMLine = Some(msection->GetLevel());
     }
 
-    nsresult rv = BindTrackToMsection(&(*track), msection);
-    NS_ENSURE_SUCCESS(rv, rv);
+    track.mTrack->AddToOffer(msection);
   }
   return NS_OK;
 }
 
 nsresult
-JsepSessionImpl::BindTrackToMsection(
-    JsepSendingTrack* track,
-    SdpMediaSection* msection)
+JsepSessionImpl::BindRemoteTracks(SdpMediaSection::MediaType mediatype,
+                                  Sdp* sdp,
+                                  size_t* offerToReceive)
 {
-  if (msection->GetMediaType() != SdpMediaSection::kApplication) {
-    mSdpHelper.SetSsrcs(track->mTrack->GetSsrcs(), mCNAME, msection);
-    AddLocalIds(*track->mTrack, msection);
-  }
-  msection->SetSending(true);
-  track->mAssignedMLine = Some(msection->GetLevel());
-  track->mNegotiated = false;
-  return NS_OK;
-}
-
-nsresult
-JsepSessionImpl::EnsureRecvForRemoteTracks(SdpMediaSection::MediaType mediatype,
-                                           Sdp* sdp,
-                                           size_t* offerToReceive)
-{
-  for (auto track = mRemoteTracks.begin(); track != mRemoteTracks.end();
-       ++track) {
-    if (mediatype != track->mTrack->GetMediaType()) {
+  for (JsepReceivingTrack& track : mRemoteTracks) {
+    if (mediatype != track.mTrack->GetMediaType()) {
       continue;
     }
 
-    if (!track->mAssignedMLine.isSome()) {
+    if (!track.mAssignedMLine.isSome()) {
       MOZ_ASSERT(false);
       continue;
     }
 
-    auto& msection = sdp->GetMediaSection(*track->mAssignedMLine);
+    auto& msection = sdp->GetMediaSection(*track.mAssignedMLine);
 
     if (mSdpHelper.MsectionIsDisabled(msection)) {
       // TODO(bug 1095226) Content probably disabled this? Should we allow
       // content to do this?
       continue;
     }
 
-    msection.SetReceiving(true);
+    track.mTrack->AddToOffer(&msection);
+
     if (offerToReceive && *offerToReceive) {
       --(*offerToReceive);
     }
   }
 
   return NS_OK;
 }
 
@@ -460,47 +432,61 @@ JsepSessionImpl::SetRecvAsNeededOrDisabl
     if (mSdpHelper.MsectionIsDisabled(msection) ||
         msection.GetMediaType() != mediatype ||
         msection.IsReceiving()) {
       continue;
     }
 
     if (offerToRecv) {
       if (*offerToRecv) {
-        msection.SetReceiving(true);
+        SetupOfferToReceiveMsection(&msection);
         --(*offerToRecv);
         continue;
       }
     } else if (msection.IsSending()) {
-      msection.SetReceiving(true);
+      SetupOfferToReceiveMsection(&msection);
       continue;
     }
 
     if (!msection.IsSending()) {
       // Unused m-section, and no reason to offer to recv on it
       mSdpHelper.DisableMsection(sdp, &msection);
     }
   }
 
   return NS_OK;
 }
 
+void
+JsepSessionImpl::SetupOfferToReceiveMsection(SdpMediaSection* offer)
+{
+  // Create a dummy recv track, and have it add codecs, set direction, etc.
+  RefPtr<JsepTrack> dummy = new JsepTrack(offer->GetMediaType(),
+                                          "",
+                                          "",
+                                          sdp::kRecv);
+  dummy->PopulateCodecs(mSupportedCodecs.values);
+  dummy->AddToOffer(offer);
+}
+
 nsresult
 JsepSessionImpl::AddRecvonlyMsections(SdpMediaSection::MediaType mediatype,
                                       size_t count,
                                       Sdp* sdp)
 {
   while (count--) {
     nsresult rv = CreateOfferMSection(
         mediatype,
         mSdpHelper.GetProtocolForMediaType(mediatype),
         SdpDirectionAttribute::kRecvonly,
         sdp);
 
     NS_ENSURE_SUCCESS(rv, rv);
+    SetupOfferToReceiveMsection(
+        &sdp->GetMediaSection(sdp->GetMediaSectionCount() - 1));
   }
   return NS_OK;
 }
 
 // This function creates a skeleton SDP based on the old descriptions
 // (ie; all m-sections are inactive).
 nsresult
 JsepSessionImpl::AddReofferMsections(const Sdp& oldLocalSdp,
@@ -617,19 +603,19 @@ JsepSessionImpl::CreateOffer(const JsepO
   if (mState != kJsepStateStable) {
     JSEP_SET_ERROR("Cannot create offer in state " << GetStateStr(mState));
     return NS_ERROR_UNEXPECTED;
   }
 
   // Undo track assignments from a previous call to CreateOffer
   // (ie; if the track has not been negotiated yet, it doesn't necessarily need
   // to stay in the same m-section that it was in)
-  for (auto i = mLocalTracks.begin(); i != mLocalTracks.end(); ++i) {
-    if (!i->mNegotiated) {
-      i->mAssignedMLine.reset();
+  for (JsepSendingTrack& trackWrapper : mLocalTracks) {
+    if (!trackWrapper.mTrack->GetNegotiatedDetails()) {
+      trackWrapper.mAssignedMLine.reset();
     }
   }
 
   UniquePtr<Sdp> sdp;
 
   // Make the basic SDP that is common to offer/answer.
   nsresult rv = CreateGenericSDP(&sdp);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -679,138 +665,16 @@ JsepSessionImpl::GetRemoteDescription() 
     mPendingRemoteDescription->Serialize(os);
   } else if (mCurrentRemoteDescription) {
     mCurrentRemoteDescription->Serialize(os);
   }
   return os.str();
 }
 
 void
-JsepSessionImpl::AddCodecs(SdpMediaSection* msection) const
-{
-  msection->ClearCodecs();
-  for (const JsepCodecDescription* codec :
-       mCodecsByLevel[msection->GetLevel()]) {
-    codec->AddToMediaSection(*msection);
-  }
-}
-
-void
-JsepSessionImpl::PopulateCodecsByLevel(size_t numLevels)
-{
-  while (mCodecsByLevel.size() < numLevels) {
-    mCodecsByLevel.push_back(CreateCodecClones());
-  }
-}
-
-void
-JsepSessionImpl::UpdateCodecsForOffer(size_t level)
-{
-  if (mCodecsByLevel.size() <= level) {
-    // New m-section, populate with defaults
-    PopulateCodecsByLevel(level + 1);
-    return;
-  }
-
-  ResetNonNegotiatedCodecs(level);
-
-  EnsureNoDuplicatePayloadTypes(level);
-}
-
-void
-JsepSessionImpl::ResetNonNegotiatedCodecs(size_t level)
-{
-  for (size_t i = 0; i < mSupportedCodecs.values.size(); ++i) {
-    if (mCodecsByLevel[level][i]->mNegotiated) {
-      continue;
-    }
-
-    delete mCodecsByLevel[level][i];
-    mCodecsByLevel[level][i] = mSupportedCodecs.values[i]->Clone();
-  }
-}
-
-void
-JsepSessionImpl::GetNegotiatedPayloadTypes(size_t level,
-                                           std::set<uint16_t>* types) const
-{
-  MOZ_RELEASE_ASSERT(level < mCodecsByLevel.size());
-  MOZ_RELEASE_ASSERT(mCodecsByLevel[level].size() ==
-                     mSupportedCodecs.values.size());
-
-  for (size_t i = 0; i < mSupportedCodecs.values.size(); ++i) {
-    if (!mCodecsByLevel[level][i]->mNegotiated) {
-      continue;
-    }
-
-    uint16_t pt;
-    if (!mCodecsByLevel[level][i]->GetPtAsInt(&pt)) {
-      MOZ_ASSERT(false);
-      continue;
-    }
-    MOZ_ASSERT(!types->count(pt));
-    types->insert(pt);
-  }
-}
-
-void
-JsepSessionImpl::EnsureNoDuplicatePayloadTypes(size_t level)
-{
-  std::set<uint16_t> payloadTypes;
-  // Negotiated codecs need to keep their payload type. Codecs that were not
-  // negotiated last time can use whatever is left.
-  GetNegotiatedPayloadTypes(level, &payloadTypes);
-
-  for (JsepCodecDescription* codec : mCodecsByLevel[level]) {
-    // We assume that no duplicates were negotiated.
-    if (codec->mNegotiated || !codec->mEnabled) {
-      continue;
-    }
-
-    // Disable, and only re-enable if we can ensure it has a unique pt.
-    codec->mEnabled = false;
-
-    uint16_t currentPt;
-    if (!codec->GetPtAsInt(&currentPt)) {
-      MOZ_ASSERT(false);
-      continue;
-    }
-
-    if (!payloadTypes.count(currentPt)) {
-      codec->mEnabled = true;
-      payloadTypes.insert(currentPt);
-      continue;
-    }
-
-    // |codec| cannot use its current payload type. Try to find another.
-    for (uint16_t freePt = 0; freePt <= 128; ++freePt) {
-      // Not super efficient, but readability is probably more important.
-      if (!payloadTypes.count(freePt)) {
-        payloadTypes.insert(freePt);
-        codec->mEnabled = true;
-        std::ostringstream os;
-        os << freePt;
-        codec->mDefaultPt = os.str();
-        break;
-      }
-    }
-  }
-}
-
-std::vector<JsepCodecDescription*>
-JsepSessionImpl::CreateCodecClones() const
-{
-  std::vector<JsepCodecDescription*> clones;
-  for (const JsepCodecDescription* codec : mSupportedCodecs.values) {
-    clones.push_back(codec->Clone());
-  }
-  return clones;
-}
-
-void
 JsepSessionImpl::AddExtmap(SdpMediaSection* msection) const
 {
   const auto* extensions = GetRtpExtensions(msection->GetMediaType());
 
   if (extensions && !extensions->empty()) {
     SdpExtmapAttributeList* extmap = new SdpExtmapAttributeList;
     extmap->mExtmaps = *extensions;
     msection->GetAttributeList().SetAttribute(extmap);
@@ -820,84 +684,29 @@ JsepSessionImpl::AddExtmap(SdpMediaSecti
 void
 JsepSessionImpl::AddMid(const std::string& mid,
                         SdpMediaSection* msection) const
 {
   msection->GetAttributeList().SetAttribute(new SdpStringAttribute(
         SdpAttribute::kMidAttribute, mid));
 }
 
-void
-JsepSessionImpl::AddLocalIds(const JsepTrack& track,
-                             SdpMediaSection* msection) const
-{
-  if (track.GetMediaType() == SdpMediaSection::kApplication) {
-    return;
-  }
-
-  UniquePtr<SdpMsidAttributeList> msids(new SdpMsidAttributeList);
-  if (msection->GetAttributeList().HasAttribute(SdpAttribute::kMsidAttribute)) {
-    msids->mMsids = msection->GetAttributeList().GetMsid().mMsids;
-  }
-
-  msids->PushEntry(track.GetStreamId(), track.GetTrackId());
-
-  msection->GetAttributeList().SetAttribute(msids.release());
-}
-
-JsepCodecDescription*
-JsepSessionImpl::FindMatchingCodec(const std::string& fmt,
-                                   const SdpMediaSection& msection) const
-{
-  for (JsepCodecDescription* codec : mCodecsByLevel[msection.GetLevel()]) {
-    if (codec->mEnabled && codec->Matches(fmt, msection)) {
-      return codec;
-    }
-  }
-
-  return nullptr;
-}
-
 const std::vector<SdpExtmapAttributeList::Extmap>*
 JsepSessionImpl::GetRtpExtensions(SdpMediaSection::MediaType type) const
 {
   switch (type) {
     case SdpMediaSection::kAudio:
       return &mAudioRtpExtensions;
     case SdpMediaSection::kVideo:
       return &mVideoRtpExtensions;
     default:
       return nullptr;
   }
 }
 
-static bool
-CompareCodec(const JsepCodecDescription* lhs, const JsepCodecDescription* rhs)
-{
-  return lhs->mStronglyPreferred && !rhs->mStronglyPreferred;
-}
-
-std::vector<JsepCodecDescription*>
-JsepSessionImpl::GetCommonCodecs(const SdpMediaSection& offerMsection)
-{
-  MOZ_ASSERT(!mIsOfferer);
-  std::vector<JsepCodecDescription*> matchingCodecs;
-  for (const std::string& fmt : offerMsection.GetFormats()) {
-    JsepCodecDescription* codec = FindMatchingCodec(fmt, offerMsection);
-    if (codec) {
-      codec->mDefaultPt = fmt; // Remember the other side's PT
-      matchingCodecs.push_back(codec);
-    }
-  }
-
-  std::stable_sort(matchingCodecs.begin(), matchingCodecs.end(), CompareCodec);
-
-  return matchingCodecs;
-}
-
 void
 JsepSessionImpl::AddCommonExtmaps(const SdpMediaSection& remoteMsection,
                                   SdpMediaSection* msection)
 {
   auto* ourExtensions = GetRtpExtensions(remoteMsection.GetMediaType());
 
   if (ourExtensions) {
     mSdpHelper.AddCommonExtmaps(remoteMsection, *ourExtensions, msection);
@@ -935,36 +744,34 @@ JsepSessionImpl::CreateAnswer(const Jsep
 
   // Copy the bundle groups into our answer
   UniquePtr<SdpGroupAttributeList> groupAttr(new SdpGroupAttributeList);
   mSdpHelper.GetBundleGroups(offer, &groupAttr->mGroups);
   sdp->GetAttributeList().SetAttribute(groupAttr.release());
 
   // Disable send for local tracks if the offer no longer allows it
   // (i.e., the m-section is recvonly, inactive or disabled)
-  for (auto i = mLocalTracks.begin(); i != mLocalTracks.end(); ++i) {
-    if (!i->mAssignedMLine.isSome()) {
+  for (JsepSendingTrack& trackWrapper : mLocalTracks) {
+    if (!trackWrapper.mAssignedMLine.isSome()) {
       continue;
     }
 
     // Get rid of all m-line assignments that have not been negotiated
-    if (!i->mNegotiated) {
-      i->mAssignedMLine.reset();
+    if (!trackWrapper.mTrack->GetNegotiatedDetails()) {
+      trackWrapper.mAssignedMLine.reset();
       continue;
     }
 
-    if (!offer.GetMediaSection(*i->mAssignedMLine).IsReceiving()) {
-      i->mAssignedMLine.reset();
+    if (!offer.GetMediaSection(*trackWrapper.mAssignedMLine).IsReceiving()) {
+      trackWrapper.mAssignedMLine.reset();
     }
   }
 
   size_t numMsections = offer.GetMediaSectionCount();
 
-  PopulateCodecsByLevel(numMsections);
-
   for (size_t i = 0; i < numMsections; ++i) {
     const SdpMediaSection& remoteMsection = offer.GetMediaSection(i);
     rv = CreateAnswerMSection(options, i, remoteMsection, sdp.get());
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   if (mCurrentLocalDescription) {
     rv = CopyPreviousTransportParams(*GetAnswer(), *sdp, sdp.get());
@@ -1060,45 +867,32 @@ JsepSessionImpl::CreateAnswerMSection(co
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = SetRecvonlySsrc(&msection);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Only attempt to match up local tracks if the offerer has elected to
   // receive traffic.
   if (remoteMsection.IsReceiving()) {
-    rv = BindMatchingLocalTrackForAnswer(&msection);
+    rv = BindMatchingLocalTrackToAnswer(&msection);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   if (remoteMsection.IsSending()) {
-    msection.SetReceiving(true);
+    BindMatchingRemoteTrackToAnswer(&msection);
   }
 
-  // Now add the codecs.
-  std::vector<JsepCodecDescription*> matchingCodecs(
-      GetCommonCodecs(remoteMsection));
-
-  for (JsepCodecDescription* codec : matchingCodecs) {
-    if (codec->Negotiate(remoteMsection)) {
-      codec->AddToMediaSection(msection);
-      // TODO(bug 1099351): Once bug 1073475 is fixed on all supported
-      // versions, we can remove this limitation.
-      break;
-    }
-  }
-
-  // Add extmap attributes.
-  AddCommonExtmaps(remoteMsection, &msection);
-
   if (!msection.IsReceiving() && !msection.IsSending()) {
     mSdpHelper.DisableMsection(sdp, &msection);
     return NS_OK;
   }
 
+  // Add extmap attributes.
+  AddCommonExtmaps(remoteMsection, &msection);
+
   if (msection.GetFormats().empty()) {
     // Could not negotiate anything. Disable m-section.
     mSdpHelper.DisableMsection(sdp, &msection);
   }
 
   return NS_OK;
 }
 
@@ -1110,22 +904,22 @@ JsepSessionImpl::SetRecvonlySsrc(SdpMedi
     uint32_t ssrc;
     nsresult rv = CreateSsrc(&ssrc);
     NS_ENSURE_SUCCESS(rv, rv);
     mRecvonlySsrcs.push_back(ssrc);
   }
 
   std::vector<uint32_t> ssrcs;
   ssrcs.push_back(mRecvonlySsrcs[msection->GetLevel()]);
-  mSdpHelper.SetSsrcs(ssrcs, mCNAME, msection);
+  msection->SetSsrcs(ssrcs, mCNAME);
   return NS_OK;
 }
 
 nsresult
-JsepSessionImpl::BindMatchingLocalTrackForAnswer(SdpMediaSection* msection)
+JsepSessionImpl::BindMatchingLocalTrackToAnswer(SdpMediaSection* msection)
 {
   auto track = FindTrackByLevel(mLocalTracks, msection->GetLevel());
 
   if (track == mLocalTracks.end()) {
     track = FindUnassignedTrackByType(mLocalTracks, msection->GetMediaType());
   }
 
   if (track == mLocalTracks.end() &&
@@ -1142,24 +936,42 @@ JsepSessionImpl::BindMatchingLocalTrackF
 
     AddTrack(RefPtr<JsepTrack>(
           new JsepTrack(SdpMediaSection::kApplication, streamId, trackId)));
     track = FindUnassignedTrackByType(mLocalTracks, msection->GetMediaType());
     MOZ_ASSERT(track != mLocalTracks.end());
   }
 
   if (track != mLocalTracks.end()) {
-    nsresult rv = BindTrackToMsection(&(*track), msection);
-    NS_ENSURE_SUCCESS(rv, rv);
+    track->mAssignedMLine = Some(msection->GetLevel());
+    track->mTrack->AddToAnswer(
+        mPendingRemoteDescription->GetMediaSection(msection->GetLevel()),
+        msection);
   }
 
   return NS_OK;
 }
 
 nsresult
+JsepSessionImpl::BindMatchingRemoteTrackToAnswer(SdpMediaSection* msection)
+{
+  auto it = FindTrackByLevel(mRemoteTracks, msection->GetLevel());
+  if (it == mRemoteTracks.end()) {
+    MOZ_ASSERT(false);
+    JSEP_SET_ERROR("Failed to find remote track for local answer m-section");
+    return NS_ERROR_FAILURE;
+  }
+
+  it->mTrack->AddToAnswer(
+      mPendingRemoteDescription->GetMediaSection(msection->GetLevel()),
+      msection);
+  return NS_OK;
+}
+
+nsresult
 JsepSessionImpl::DetermineAnswererSetupRole(
     const SdpMediaSection& remoteMsection,
     SdpSetupAttribute::Role* rolep)
 {
   // Determine the role.
   // RFC 5763 says:
   //
   //   The endpoint MUST use the setup attribute defined in [RFC4145].
@@ -1403,24 +1215,32 @@ JsepSessionImpl::HandleNegotiatedSession
   mIceControlling = remoteIceLite || mIsOfferer;
 
   const Sdp& answer = mIsOfferer ? *remote : *local;
 
   SdpHelper::BundledMids bundledMids;
   nsresult rv = mSdpHelper.GetBundledMids(answer, &bundledMids);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  std::vector<JsepTrackPair> trackPairs;
-
   if (mTransports.size() < local->GetMediaSectionCount()) {
     JSEP_SET_ERROR("Fewer transports set up than m-lines");
     MOZ_ASSERT(false);
     return NS_ERROR_FAILURE;
   }
 
+  for (JsepSendingTrack& trackWrapper : mLocalTracks) {
+    trackWrapper.mTrack->ClearNegotiatedDetails();
+  }
+
+  for (JsepReceivingTrack& trackWrapper : mRemoteTracks) {
+    trackWrapper.mTrack->ClearNegotiatedDetails();
+  }
+
+  std::vector<JsepTrackPair> trackPairs;
+
   // Now walk through the m-sections, make sure they match, and create
   // track pairs that describe the media to be set up.
   for (size_t i = 0; i < local->GetMediaSectionCount(); ++i) {
     // Skip disabled m-sections.
     if (answer.GetMediaSection(i).GetPort() == 0) {
       mTransports[i]->Close();
       continue;
     }
@@ -1467,30 +1287,22 @@ JsepSessionImpl::HandleNegotiatedSession
                                  usingBundle,
                                  transportLevel,
                                  &trackPair);
     NS_ENSURE_SUCCESS(rv, rv);
 
     trackPairs.push_back(trackPair);
   }
 
-  rv = SetUniquePayloadTypes();
-  NS_ENSURE_SUCCESS(rv, rv);
+  JsepTrack::SetUniquePayloadTypes(GetTracks(mRemoteTracks));
 
   // Ouch, this probably needs some dirty bit instead of just clearing
   // stuff for renegotiation.
   mNegotiatedTrackPairs = trackPairs;
 
-  // Mark all assigned local tracks as negotiated
-  for (auto i = mLocalTracks.begin(); i != mLocalTracks.end(); ++i) {
-    if (i->mAssignedMLine.isSome()) {
-      i->mNegotiated = true;
-    }
-  }
-
   mGeneratedLocalDescription.reset();
   return NS_OK;
 }
 
 nsresult
 JsepSessionImpl::MakeNegotiatedTrackPair(const SdpMediaSection& remote,
                                          const SdpMediaSection& local,
                                          const RefPtr<JsepTransport>& transport,
@@ -1533,47 +1345,32 @@ JsepSessionImpl::MakeNegotiatedTrackPair
     if (sendTrack == mLocalTracks.end()) {
       JSEP_SET_ERROR("Failed to find local track for level " <<
                      local.GetLevel()
                      << " in local SDP. This should never happen.");
       NS_ASSERTION(false, "Failed to find local track for level");
       return NS_ERROR_FAILURE;
     }
 
-    nsresult rv = NegotiateTrack(remote,
-                                 local,
-                                 JsepTrack::kJsepTrackSending,
-                                 &sendTrack->mTrack);
-    NS_ENSURE_SUCCESS(rv, rv);
+    sendTrack->mTrack->Negotiate(answer, remote);
 
     trackPairOut->mSending = sendTrack->mTrack;
   }
 
   if (receiving) {
     auto recvTrack = FindTrackByLevel(mRemoteTracks, local.GetLevel());
     if (recvTrack == mRemoteTracks.end()) {
       JSEP_SET_ERROR("Failed to find remote track for level "
                      << local.GetLevel()
                      << " in remote SDP. This should never happen.");
       NS_ASSERTION(false, "Failed to find remote track for level");
       return NS_ERROR_FAILURE;
     }
 
-    nsresult rv = NegotiateTrack(remote,
-                                 local,
-                                 JsepTrack::kJsepTrackReceiving,
-                                 &recvTrack->mTrack);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    if (remote.GetAttributeList().HasAttribute(SdpAttribute::kSsrcAttribute)) {
-      auto& ssrcs = remote.GetAttributeList().GetSsrc().mSsrcs;
-      for (auto i = ssrcs.begin(); i != ssrcs.end(); ++i) {
-        recvTrack->mTrack->AddSsrc(i->ssrc);
-      }
-    }
+    recvTrack->mTrack->Negotiate(answer, remote);
 
     if (trackPairOut->mBundleLevel.isSome() &&
         recvTrack->mTrack->GetSsrcs().empty() &&
         recvTrack->mTrack->GetMediaType() != SdpMediaSection::kApplication) {
       MOZ_MTLOG(ML_ERROR, "Bundled m-section has no ssrc attributes. "
                           "This may cause media packets to be dropped.");
     }
 
@@ -1587,97 +1384,16 @@ JsepSessionImpl::MakeNegotiatedTrackPair
     // TODO(bug 1095743): verify that the PTs are consistent with mux.
     MOZ_MTLOG(ML_DEBUG, "RTCP-MUX is off");
     trackPairOut->mRtcpTransport = transport;
   }
 
   return NS_OK;
 }
 
-nsresult
-JsepSessionImpl::NegotiateTrack(const SdpMediaSection& remoteMsection,
-                                const SdpMediaSection& localMsection,
-                                JsepTrack::Direction direction,
-                                RefPtr<JsepTrack>* track)
-{
-  UniquePtr<JsepTrackNegotiatedDetailsImpl> negotiatedDetails =
-      MakeUnique<JsepTrackNegotiatedDetailsImpl>();
-  negotiatedDetails->mProtocol = remoteMsection.GetProtocol();
-
-  auto& answerMsection = mIsOfferer ? remoteMsection : localMsection;
-
-  for (auto& format : answerMsection.GetFormats()) {
-    JsepCodecDescription* origCodec = FindMatchingCodec(format, answerMsection);
-    if (!origCodec) {
-      continue;
-    }
-
-    // Make sure codec->mDefaultPt is consistent with what is in the remote
-    // msection, since the following logic needs to look stuff up there.
-    for (auto& remoteFormat : remoteMsection.GetFormats()) {
-      if (origCodec->Matches(remoteFormat, remoteMsection)) {
-        origCodec->mDefaultPt = remoteFormat;
-        break;
-      }
-    }
-
-    UniquePtr<JsepCodecDescription> codec(origCodec->Clone());
-
-    bool sending = (direction == JsepTrack::kJsepTrackSending);
-
-    // Everywhere else in JsepSessionImpl, a JsepCodecDescription describes
-    // what one side puts in its SDP. However, we don't want that here; we want
-    // a JsepCodecDescription that instead encapsulates all the parameters
-    // that deal with sending (or receiving). For sending, some of these
-    // parameters will come from the remote SDP (eg; max-fps), and others can
-    // only be determined by inspecting both local config and remote SDP (eg;
-    // profile-level-id when level-asymmetry-allowed is 0).
-    if (sending) {
-      if (!codec->LoadSendParameters(remoteMsection)) {
-        continue;
-      }
-    } else {
-      if (!codec->LoadRecvParameters(remoteMsection)) {
-        continue;
-      }
-    }
-
-    if (remoteMsection.GetMediaType() == SdpMediaSection::kAudio ||
-        remoteMsection.GetMediaType() == SdpMediaSection::kVideo) {
-      // Sanity-check that payload type can work with RTP
-      uint16_t payloadType;
-      if (!codec->GetPtAsInt(&payloadType) ||
-          payloadType > UINT8_MAX) {
-        JSEP_SET_ERROR("audio/video payload type is not an 8 bit unsigned int: "
-                       << codec->mDefaultPt);
-        return NS_ERROR_INVALID_ARG;
-      }
-    }
-
-    negotiatedDetails->mCodecs.values.push_back(codec.release());
-    break;
-  }
-
-  if (negotiatedDetails->mCodecs.values.empty()) {
-    JSEP_SET_ERROR("Failed to negotiate codec details for all codecs");
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  if (answerMsection.GetAttributeList().HasAttribute(
-        SdpAttribute::kExtmapAttribute)) {
-    auto& extmap = answerMsection.GetAttributeList().GetExtmap().mExtmaps;
-    for (auto i = extmap.begin(); i != extmap.end(); ++i) {
-      negotiatedDetails->mExtmap[i->extensionname] = *i;
-    }
-  }
-
-  (*track)->SetNegotiatedDetails(Move(negotiatedDetails));
-  return NS_OK;
-}
-
 void
 JsepSessionImpl::UpdateTransport(const SdpMediaSection& msection,
                                  JsepTransport* transport)
 {
   if (mSdpHelper.MsectionIsDisabled(msection)) {
     transport->Close();
     return;
   }
@@ -1814,17 +1530,18 @@ JsepSessionImpl::ParseSdp(const std::str
   std::set<std::string> trackIds;
 
   for (size_t i = 0; i < parsed->GetMediaSectionCount(); ++i) {
     if (mSdpHelper.MsectionIsDisabled(parsed->GetMediaSection(i))) {
       // Disabled, let this stuff slide.
       continue;
     }
 
-    auto& mediaAttrs = parsed->GetMediaSection(i).GetAttributeList();
+    const SdpMediaSection& msection(parsed->GetMediaSection(i));
+    auto& mediaAttrs = msection.GetAttributeList();
 
     if (mediaAttrs.GetIceUfrag().empty()) {
       JSEP_SET_ERROR("Invalid description, no ice-ufrag attribute");
       return NS_ERROR_INVALID_ARG;
     }
 
     if (mediaAttrs.GetIcePwd().empty()) {
       JSEP_SET_ERROR("Invalid description, no ice-pwd attribute");
@@ -1877,16 +1594,29 @@ JsepSessionImpl::ParseSdp(const std::str
         return NS_ERROR_INVALID_ARG;
       }
 
       trackIds.insert(trackId);
     } else if (rv != NS_ERROR_NOT_AVAILABLE) {
       // Error has already been set
       return rv;
     }
+
+    if (msection.GetMediaType() == SdpMediaSection::kAudio ||
+        msection.GetMediaType() == SdpMediaSection::kVideo) {
+      // Sanity-check that payload type can work with RTP
+      for (const std::string& fmt : msection.GetFormats()) {
+        uint16_t payloadType;
+        // TODO (bug 1204099): Make this check for reserved ranges.
+        if (!SdpHelper::GetPtAsInt(fmt, &payloadType) || payloadType > 127) {
+          JSEP_SET_ERROR("audio/video payload type is too large: " << fmt);
+          return NS_ERROR_INVALID_ARG;
+        }
+      }
+    }
   }
 
   *parsedp = Move(parsed);
   return NS_OK;
 }
 
 nsresult
 JsepSessionImpl::SetRemoteDescriptionOffer(UniquePtr<Sdp> offer)
@@ -2165,19 +1895,20 @@ JsepSessionImpl::CreateReceivingTrack(si
   std::string trackId;
 
   nsresult rv = GetRemoteIds(sdp, msection, &streamId, &trackId);
   NS_ENSURE_SUCCESS(rv, rv);
 
   *track = new JsepTrack(msection.GetMediaType(),
                          streamId,
                          trackId,
-                         JsepTrack::kJsepTrackReceiving);
+                         sdp::kRecv);
 
   (*track)->SetCNAME(mSdpHelper.GetCNAME(msection));
+  (*track)->PopulateCodecs(mSupportedCodecs.values);
 
   return NS_OK;
 }
 
 nsresult
 JsepSessionImpl::CreateGenericSDP(UniquePtr<Sdp>* sdpp)
 {
   // draft-ietf-rtcweb-jsep-08 Section 5.2.1:
@@ -2527,119 +2258,25 @@ JsepSessionImpl::EnableOfferMsection(Sdp
   }
 
   nsresult rv = AddTransportAttributes(msection, SdpSetupAttribute::kActpass);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = SetRecvonlySsrc(msection);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  UpdateCodecsForOffer(msection->GetLevel());
-
-  AddCodecs(msection);
-
   AddExtmap(msection);
 
   std::ostringstream osMid;
   osMid << "sdparta_" << msection->GetLevel();
   AddMid(osMid.str(), msection);
 
   return NS_OK;
 }
 
-nsresult
-JsepSessionImpl::GetAllPayloadTypes(
-    const JsepTrackNegotiatedDetails& trackDetails,
-    std::vector<uint8_t>* payloadTypesOut)
-{
-  for (size_t j = 0; j < trackDetails.GetCodecCount(); ++j) {
-    const JsepCodecDescription* codec;
-    nsresult rv = trackDetails.GetCodec(j, &codec);
-    if (NS_FAILED(rv)) {
-      JSEP_SET_ERROR("GetCodec failed in GetAllPayloadTypes. rv="
-                     << static_cast<uint32_t>(rv));
-      MOZ_ASSERT(false);
-      return NS_ERROR_FAILURE;
-    }
-
-    uint16_t payloadType;
-    if (!codec->GetPtAsInt(&payloadType) || payloadType > UINT8_MAX) {
-      JSEP_SET_ERROR("Non-UINT8 payload type in GetAllPayloadTypes ("
-                     << codec->mType
-                     << "), this should have been caught sooner.");
-      MOZ_ASSERT(false);
-      return NS_ERROR_INVALID_ARG;
-    }
-
-    payloadTypesOut->push_back(payloadType);
-  }
-
-  return NS_OK;
-}
-
-// When doing bundle, if all else fails we can try to figure out which m-line a
-// given RTP packet belongs to by looking at the payload type field. This only
-// works, however, if that payload type appeared in only one m-section.
-// We figure that out here.
-nsresult
-JsepSessionImpl::SetUniquePayloadTypes()
-{
-  // Maps to track details if no other track contains the payload type,
-  // otherwise maps to nullptr.
-  std::map<uint8_t, JsepTrackNegotiatedDetails*> payloadTypeToDetailsMap;
-
-  for (size_t i = 0; i < mRemoteTracks.size(); ++i) {
-    auto track = mRemoteTracks[i].mTrack;
-
-    if (track->GetMediaType() == SdpMediaSection::kApplication) {
-      continue;
-    }
-
-    auto* details = track->GetNegotiatedDetails();
-    if (!details) {
-      // Can happen if negotiation fails on a track
-      continue;
-    }
-
-    // Renegotiation might cause a PT to no longer be unique
-    details->ClearUniquePayloadTypes();
-
-    std::vector<uint8_t> payloadTypesForTrack;
-    nsresult rv = GetAllPayloadTypes(*details, &payloadTypesForTrack);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    for (auto j = payloadTypesForTrack.begin();
-         j != payloadTypesForTrack.end();
-         ++j) {
-
-      if (payloadTypeToDetailsMap.count(*j)) {
-        // Found in more than one track, not unique
-        payloadTypeToDetailsMap[*j] = nullptr;
-      } else {
-        payloadTypeToDetailsMap[*j] = details;
-      }
-    }
-  }
-
-  for (auto i = payloadTypeToDetailsMap.begin();
-       i != payloadTypeToDetailsMap.end();
-       ++i) {
-    uint8_t uniquePt = i->first;
-    auto trackDetails = i->second;
-
-    if (!trackDetails) {
-      continue;
-    }
-
-    trackDetails->AddUniquePayloadType(uniquePt);
-  }
-
-  return NS_OK;
-}
-
 const Sdp*
 JsepSessionImpl::GetAnswer() const
 {
   return mWasOffererLastTime ? mCurrentRemoteDescription.get()
                              : mCurrentLocalDescription.get();
 }
 
 nsresult
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.h
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.h
@@ -8,17 +8,16 @@
 #include <set>
 #include <string>
 #include <vector>
 
 #include "signaling/src/jsep/JsepCodecDescription.h"
 #include "signaling/src/jsep/JsepTrack.h"
 #include "signaling/src/jsep/JsepSession.h"
 #include "signaling/src/jsep/JsepTrack.h"
-#include "signaling/src/jsep/JsepTrackImpl.h"
 #include "signaling/src/sdp/SipccSdpParser.h"
 #include "signaling/src/sdp/SdpHelper.h"
 #include "signaling/src/common/PtrVector.h"
 
 namespace mozilla {
 
 class JsepUuidGenerator
 {
@@ -39,18 +38,16 @@ public:
         mBundlePolicy(kBundleBalanced),
         mSessionId(0),
         mSessionVersion(0),
         mUuidGen(Move(uuidgen)),
         mSdpHelper(&mLastError)
   {
   }
 
-  virtual ~JsepSessionImpl();
-
   // Implement JsepSession methods.
   virtual nsresult Init() override;
 
   virtual nsresult AddTrack(const RefPtr<JsepTrack>& track) override;
 
   virtual nsresult RemoveTrack(const std::string& streamId,
                                const std::string& trackId) override;
 
@@ -166,45 +163,30 @@ private:
   struct JsepDtlsFingerprint {
     std::string mAlgorithm;
     std::vector<uint8_t> mValue;
   };
 
   struct JsepSendingTrack {
     RefPtr<JsepTrack> mTrack;
     Maybe<size_t> mAssignedMLine;
-    bool mNegotiated;
   };
 
   struct JsepReceivingTrack {
     RefPtr<JsepTrack> mTrack;
     Maybe<size_t> mAssignedMLine;
   };
 
   // Non-const so it can set mLastError
   nsresult CreateGenericSDP(UniquePtr<Sdp>* sdp);
-  void AddCodecs(SdpMediaSection* msection) const;
-  void PopulateCodecsByLevel(size_t numLevels);
-  void UpdateCodecsForOffer(size_t level);
-  void ResetNonNegotiatedCodecs(size_t level);
-  void GetNegotiatedPayloadTypes(size_t level,
-                                 std::set<uint16_t>* types) const;
-  void EnsureNoDuplicatePayloadTypes(size_t level);
-  std::vector<JsepCodecDescription*> CreateCodecClones() const;
   void AddExtmap(SdpMediaSection* msection) const;
   void AddMid(const std::string& mid, SdpMediaSection* msection) const;
-  void AddLocalIds(const JsepTrack& track, SdpMediaSection* msection) const;
-  JsepCodecDescription* FindMatchingCodec(
-      const std::string& pt,
-      const SdpMediaSection& msection) const;
   const std::vector<SdpExtmapAttributeList::Extmap>* GetRtpExtensions(
       SdpMediaSection::MediaType type) const;
 
-  std::vector<JsepCodecDescription*> GetCommonCodecs(
-      const SdpMediaSection& offerMsection);
   void AddCommonExtmaps(const SdpMediaSection& remoteMsection,
                         SdpMediaSection* msection);
   nsresult SetupIds();
   nsresult CreateSsrc(uint32_t* ssrc);
   void SetupDefaultCodecs();
   void SetupDefaultRtpExtensions();
   void SetState(JsepSignalingState state);
   // Non-const so it can set mLastError
@@ -231,24 +213,23 @@ private:
                                        Sdp* newLocal);
   nsresult SetupOfferMSections(const JsepOfferOptions& options, Sdp* sdp);
   // Non-const so it can assign m-line index to tracks
   nsresult SetupOfferMSectionsByType(SdpMediaSection::MediaType type,
                                      Maybe<size_t> offerToReceive,
                                      Sdp* sdp);
   nsresult BindLocalTracks(SdpMediaSection::MediaType mediatype,
                            Sdp* sdp);
-  nsresult BindTrackToMsection(JsepSendingTrack* track,
-                               SdpMediaSection* msection);
-  nsresult EnsureRecvForRemoteTracks(SdpMediaSection::MediaType mediatype,
-                                     Sdp* sdp,
-                                     size_t* offerToReceive);
+  nsresult BindRemoteTracks(SdpMediaSection::MediaType mediatype,
+                            Sdp* sdp,
+                            size_t* offerToReceive);
   nsresult SetRecvAsNeededOrDisable(SdpMediaSection::MediaType mediatype,
                                     Sdp* sdp,
                                     size_t* offerToRecv);
+  void SetupOfferToReceiveMsection(SdpMediaSection* offer);
   nsresult AddRecvonlyMsections(SdpMediaSection::MediaType mediatype,
                                 size_t count,
                                 Sdp* sdp);
   nsresult AddReofferMsections(const Sdp& oldLocalSdp,
                                const Sdp& oldAnswer,
                                Sdp* newSdp);
   void SetupBundle(Sdp* sdp) const;
   nsresult GetRemoteIds(const Sdp& sdp,
@@ -262,44 +243,37 @@ private:
   nsresult GetFreeMsectionForSend(SdpMediaSection::MediaType type,
                                   Sdp* sdp,
                                   SdpMediaSection** msection);
   nsresult CreateAnswerMSection(const JsepAnswerOptions& options,
                                 size_t mlineIndex,
                                 const SdpMediaSection& remoteMsection,
                                 Sdp* sdp);
   nsresult SetRecvonlySsrc(SdpMediaSection* msection);
-  nsresult BindMatchingLocalTrackForAnswer(SdpMediaSection* msection);
+  nsresult BindMatchingLocalTrackToAnswer(SdpMediaSection* msection);
+  nsresult BindMatchingRemoteTrackToAnswer(SdpMediaSection* msection);
   nsresult DetermineAnswererSetupRole(const SdpMediaSection& remoteMsection,
                                       SdpSetupAttribute::Role* rolep);
   nsresult MakeNegotiatedTrackPair(const SdpMediaSection& remote,
                                    const SdpMediaSection& local,
                                    const RefPtr<JsepTransport>& transport,
                                    bool usingBundle,
                                    size_t transportLevel,
                                    JsepTrackPair* trackPairOut);
-  nsresult NegotiateTrack(const SdpMediaSection& remoteMsection,
-                          const SdpMediaSection& localMsection,
-                          JsepTrack::Direction,
-                          RefPtr<JsepTrack>* track);
-
   void UpdateTransport(const SdpMediaSection& msection,
                        JsepTransport* transport);
 
   nsresult FinalizeTransport(const SdpAttributeList& remote,
                              const SdpAttributeList& answer,
                              const RefPtr<JsepTransport>& transport);
 
   nsresult GetNegotiatedBundledMids(SdpHelper::BundledMids* bundledMids);
 
   nsresult EnableOfferMsection(SdpMediaSection* msection);
 
-  nsresult SetUniquePayloadTypes();
-  nsresult GetAllPayloadTypes(const JsepTrackNegotiatedDetails& trackDetails,
-                              std::vector<uint8_t>* payloadTypesOut);
   const Sdp* GetAnswer() const;
 
   std::vector<JsepSendingTrack> mLocalTracks;
   std::vector<JsepReceivingTrack> mRemoteTracks;
   // By the most recent SetRemoteDescription
   std::vector<JsepReceivingTrack> mRemoteTracksAdded;
   std::vector<JsepReceivingTrack> mRemoteTracksRemoved;
   std::vector<RefPtr<JsepTransport> > mTransports;
@@ -331,20 +305,16 @@ private:
   // is stored here.
   std::vector<uint32_t> mRecvonlySsrcs;
   UniquePtr<Sdp> mGeneratedLocalDescription; // Created but not set.
   UniquePtr<Sdp> mCurrentLocalDescription;
   UniquePtr<Sdp> mCurrentRemoteDescription;
   UniquePtr<Sdp> mPendingLocalDescription;
   UniquePtr<Sdp> mPendingRemoteDescription;
   PtrVector<JsepCodecDescription> mSupportedCodecs;
-  // For each level, contains a full clone of
-  // mSupportedCodecs. If any have been negotiated, this negotiation is taken
-  // into account.
-  std::vector<std::vector<JsepCodecDescription*>> mCodecsByLevel;
   std::string mLastError;
   SipccSdpParser mParser;
   SdpHelper mSdpHelper;
 };
 
 } // namespace mozilla
 
 #endif
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/jsep/JsepTrack.cpp
@@ -0,0 +1,303 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "signaling/src/jsep/JsepTrack.h"
+#include "signaling/src/jsep/JsepCodecDescription.h"
+
+#include <algorithm>
+
+namespace mozilla
+{
+void
+JsepTrack::GetNegotiatedPayloadTypes(std::vector<uint16_t>* payloadTypes)
+{
+  if (!mNegotiatedDetails) {
+    return;
+  }
+
+  GetPayloadTypes(mNegotiatedDetails->mCodecs.values, payloadTypes);
+}
+
+/* static */
+void
+JsepTrack::GetPayloadTypes(
+    const std::vector<JsepCodecDescription*>& codecs,
+    std::vector<uint16_t>* payloadTypes)
+{
+  for (JsepCodecDescription* codec : codecs) {
+    uint16_t pt;
+    if (!codec->GetPtAsInt(&pt)) {
+      MOZ_ASSERT(false);
+      continue;
+    }
+    payloadTypes->push_back(pt);
+  }
+}
+
+void
+JsepTrack::EnsureNoDuplicatePayloadTypes(
+    std::vector<JsepCodecDescription*>* codecs)
+{
+  std::set<uint16_t> uniquePayloadTypes;
+
+  for (JsepCodecDescription* codec : *codecs) {
+    // We assume there are no dupes in negotiated codecs; unnegotiated codecs
+    // need to change if there is a clash.
+    if (!codec->mEnabled) {
+      continue;
+    }
+
+    // Disable, and only re-enable if we can ensure it has a unique pt.
+    codec->mEnabled = false;
+
+    uint16_t currentPt;
+    if (!codec->GetPtAsInt(&currentPt)) {
+      MOZ_ASSERT(false);
+      continue;
+    }
+
+    if (!uniquePayloadTypes.count(currentPt)) {
+      codec->mEnabled = true;
+      uniquePayloadTypes.insert(currentPt);
+      continue;
+    }
+
+    // |codec| cannot use its current payload type. Try to find another.
+    for (uint16_t freePt = 96; freePt <= 127; ++freePt) {
+      // Not super efficient, but readability is probably more important.
+      if (!uniquePayloadTypes.count(freePt)) {
+        uniquePayloadTypes.insert(freePt);
+        codec->mEnabled = true;
+        std::ostringstream os;
+        os << freePt;
+        codec->mDefaultPt = os.str();
+        break;
+      }
+    }
+  }
+}
+
+void
+JsepTrack::PopulateCodecs(const std::vector<JsepCodecDescription*>& prototype)
+{
+  for (const JsepCodecDescription* prototypeCodec : prototype) {
+    if (prototypeCodec->mType == mType) {
+      mPrototypeCodecs.values.push_back(prototypeCodec->Clone());
+      mPrototypeCodecs.values.back()->mDirection = mDirection;
+    }
+  }
+
+  EnsureNoDuplicatePayloadTypes(&mPrototypeCodecs.values);
+}
+
+void
+JsepTrack::AddToOffer(SdpMediaSection* offer) const
+{
+  AddToMsection(mPrototypeCodecs.values, offer);
+}
+
+void
+JsepTrack::AddToAnswer(const SdpMediaSection& offer,
+                       SdpMediaSection* answer) const
+{
+  // We do not modify mPrototypeCodecs here, since we're only creating an answer. Once
+  // offer/answer concludes, we will update mPrototypeCodecs.
+  PtrVector<JsepCodecDescription> codecs;
+  codecs.values = GetCodecClones();
+  NegotiateCodecs(offer, &codecs.values);
+  if (codecs.values.empty()) {
+    return;
+  }
+
+  AddToMsection(codecs.values, answer);
+}
+
+void
+JsepTrack::AddToMsection(const std::vector<JsepCodecDescription*>& codecs,
+                         SdpMediaSection* msection) const
+{
+  MOZ_ASSERT(msection->GetMediaType() == mType);
+  MOZ_ASSERT(!codecs.empty());
+
+  for (const JsepCodecDescription* codec : codecs) {
+    codec->AddToMediaSection(*msection);
+  }
+
+  if (mDirection == sdp::kSend) {
+    if (msection->GetMediaType() != SdpMediaSection::kApplication) {
+      msection->SetSsrcs(mSsrcs, mCNAME);
+      msection->AddMsid(mStreamId, mTrackId);
+    }
+    msection->SetSending(true);
+  } else {
+    msection->SetReceiving(true);
+  }
+}
+
+std::vector<JsepCodecDescription*>
+JsepTrack::GetCodecClones() const
+{
+  std::vector<JsepCodecDescription*> clones;
+  for (const JsepCodecDescription* codec : mPrototypeCodecs.values) {
+    clones.push_back(codec->Clone());
+  }
+  return clones;
+}
+
+static bool
+CompareCodec(const JsepCodecDescription* lhs, const JsepCodecDescription* rhs)
+{
+  return lhs->mStronglyPreferred && !rhs->mStronglyPreferred;
+}
+
+void
+JsepTrack::NegotiateCodecs(
+    const SdpMediaSection& remote,
+    std::vector<JsepCodecDescription*>* codecs,
+    const SdpMediaSection* answer,
+    std::map<std::string, std::string>* formatChanges) const
+{
+  PtrVector<JsepCodecDescription> unnegotiatedCodecs;
+  std::swap(unnegotiatedCodecs.values, *codecs);
+
+  // Outer loop establishes the remote side's preference
+  for (const std::string& fmt : remote.GetFormats()) {
+    for (size_t i = 0; i < unnegotiatedCodecs.values.size(); ++i) {
+      JsepCodecDescription* codec = unnegotiatedCodecs.values[i];
+      if (!codec || !codec->mEnabled || !codec->Matches(fmt, remote)) {
+        continue;
+      }
+
+      std::string originalFormat = codec->mDefaultPt;
+      if(codec->Negotiate(fmt, remote)) {
+        codecs->push_back(codec);
+        unnegotiatedCodecs.values[i] = nullptr;
+        if (answer) {
+          // Answer's formats are authoritative, and they might be different
+          for (const std::string& answerFmt : answer->GetFormats()) {
+            if (codec->Matches(answerFmt, *answer)) {
+              codec->mDefaultPt = answerFmt;
+              break; // We found the corresponding format in |answer|, bail
+            }
+          }
+        }
+        if (formatChanges) {
+          (*formatChanges)[originalFormat] = codec->mDefaultPt;
+        }
+        break;
+      }
+    }
+  }
+
+  // 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()) {
+    for (size_t i = 1; i < codecs->size(); ++i) {
+      delete (*codecs)[i];
+      (*codecs)[i] = nullptr;
+    }
+    codecs->resize(1);
+  }
+}
+
+void
+JsepTrack::Negotiate(const SdpMediaSection& answer,
+                     const SdpMediaSection& remote)
+{
+  UniquePtr<JsepTrackNegotiatedDetails> negotiatedDetails =
+      MakeUnique<JsepTrackNegotiatedDetails>();
+
+  negotiatedDetails->mCodecs.values = GetCodecClones();
+  std::map<std::string, std::string> formatChanges;
+  NegotiateCodecs(remote,
+                  &negotiatedDetails->mCodecs.values,
+                  &answer,
+                  &formatChanges);
+
+  // Use |formatChanges| to update mPrototypeCodecs
+  size_t insertPos = 0;
+  for (size_t i = 0; i < mPrototypeCodecs.values.size(); ++i) {
+    if (formatChanges.count(mPrototypeCodecs.values[i]->mDefaultPt)) {
+      // Update the payload type to what was negotiated
+      mPrototypeCodecs.values[i]->mDefaultPt =
+        formatChanges[mPrototypeCodecs.values[i]->mDefaultPt];
+      // Move this negotiated codec up front
+      std::swap(mPrototypeCodecs.values[insertPos],
+                mPrototypeCodecs.values[i]);
+      ++insertPos;
+    }
+  }
+
+  EnsureNoDuplicatePayloadTypes(&mPrototypeCodecs.values);
+
+  if (answer.GetAttributeList().HasAttribute(SdpAttribute::kExtmapAttribute)) {
+    for (auto& extmapAttr : answer.GetAttributeList().GetExtmap().mExtmaps) {
+      negotiatedDetails->mExtmap[extmapAttr.extensionname] = extmapAttr;
+    }
+  }
+
+  if ((mDirection == sdp::kRecv) &&
+      remote.GetAttributeList().HasAttribute(SdpAttribute::kSsrcAttribute)) {
+    for (auto& ssrcAttr : remote.GetAttributeList().GetSsrc().mSsrcs) {
+      AddSsrc(ssrcAttr.ssrc);
+    }
+  }
+
+  mNegotiatedDetails = Move(negotiatedDetails);
+}
+
+// When doing bundle, if all else fails we can try to figure out which m-line a
+// given RTP packet belongs to by looking at the payload type field. This only
+// works, however, if that payload type appeared in only one m-section.
+// We figure that out here.
+/* static */
+void
+JsepTrack::SetUniquePayloadTypes(const std::vector<RefPtr<JsepTrack>>& tracks)
+{
+  // Maps to track details if no other track contains the payload type,
+  // otherwise maps to nullptr.
+  std::map<uint16_t, JsepTrackNegotiatedDetails*> payloadTypeToDetailsMap;
+
+  for (const RefPtr<JsepTrack>& track : tracks) {
+    if (track->GetMediaType() == SdpMediaSection::kApplication) {
+      continue;
+    }
+
+    auto* details = track->GetNegotiatedDetails();
+    if (!details) {
+      // Can happen if negotiation fails on a track
+      continue;
+    }
+
+    std::vector<uint16_t> payloadTypesForTrack;
+    track->GetNegotiatedPayloadTypes(&payloadTypesForTrack);
+
+    for (uint16_t pt : payloadTypesForTrack) {
+      if (payloadTypeToDetailsMap.count(pt)) {
+        // Found in more than one track, not unique
+        payloadTypeToDetailsMap[pt] = nullptr;
+      } else {
+        payloadTypeToDetailsMap[pt] = details;
+      }
+    }
+  }
+
+  for (auto ptAndDetails : payloadTypeToDetailsMap) {
+    uint16_t uniquePt = ptAndDetails.first;
+    MOZ_ASSERT(uniquePt <= UINT8_MAX);
+    auto trackDetails = ptAndDetails.second;
+
+    if (trackDetails) {
+      trackDetails->mUniquePayloadTypes.push_back(
+          static_cast<uint8_t>(uniquePt));
+    }
+  }
+}
+
+} // namespace mozilla
+
--- a/media/webrtc/signaling/src/jsep/JsepTrack.h
+++ b/media/webrtc/signaling/src/jsep/JsepTrack.h
@@ -1,65 +1,87 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef _JSEPTRACK_H_
 #define _JSEPTRACK_H_
 
 #include <string>
+#include <map>
+#include <set>
 
 #include <mozilla/RefPtr.h>
 #include <mozilla/UniquePtr.h>
 #include <mozilla/Maybe.h>
 #include "nsISupportsImpl.h"
 #include "nsError.h"
 
 #include "signaling/src/jsep/JsepTransport.h"
 #include "signaling/src/sdp/Sdp.h"
+#include "signaling/src/sdp/SdpAttribute.h"
 #include "signaling/src/sdp/SdpMediaSection.h"
+#include "signaling/src/common/PtrVector.h"
 
 namespace mozilla {
 
 // Forward reference.
-struct JsepCodecDescription;
+class JsepCodecDescription;
 
 class JsepTrackNegotiatedDetails
 {
 public:
-  virtual ~JsepTrackNegotiatedDetails() {}
+  size_t
+  GetCodecCount() const
+  {
+    return mCodecs.values.size();
+  }
+
+  const JsepCodecDescription*
+  GetCodec(size_t index) const
+  {
+    MOZ_RELEASE_ASSERT(index < mCodecs.values.size());
+    return mCodecs.values[index];
+  }
 
-  virtual mozilla::SdpMediaSection::Protocol GetProtocol() const = 0;
-  virtual Maybe<std::string> GetBandwidth(const std::string& type) const = 0;
-  virtual size_t GetCodecCount() const = 0;
-  virtual nsresult GetCodec(size_t index,
-                            const JsepCodecDescription** config) const = 0;
-  virtual const SdpExtmapAttributeList::Extmap* GetExt(
-      const std::string& ext_name) const = 0;
-  virtual std::vector<uint8_t> GetUniquePayloadTypes() const = 0;
+  const SdpExtmapAttributeList::Extmap*
+  GetExt(const std::string& ext_name) const
+  {
+    auto it = mExtmap.find(ext_name);
+    if (it != mExtmap.end()) {
+      return &it->second;
+    }
+    return nullptr;
+  }
 
-  virtual void AddUniquePayloadType(uint8_t pt) = 0;
-  virtual void ClearUniquePayloadTypes() = 0;
+  std::vector<uint8_t> GetUniquePayloadTypes() const
+  {
+    return mUniquePayloadTypes;
+  }
+
+private:
+  friend class JsepTrack;
+
+  std::map<std::string, SdpExtmapAttributeList::Extmap> mExtmap;
+  std::vector<uint8_t> mUniquePayloadTypes;
+  PtrVector<JsepCodecDescription> mCodecs;
 };
 
 class JsepTrack
 {
 public:
-  enum Direction { kJsepTrackSending, kJsepTrackReceiving };
-
   JsepTrack(mozilla::SdpMediaSection::MediaType type,
             const std::string& streamid,
             const std::string& trackid,
-            Direction direction = kJsepTrackSending)
+            sdp::Direction direction = sdp::kSend)
       : mType(type),
         mStreamId(streamid),
         mTrackId(trackid),
         mDirection(direction)
-  {
-  }
+  {}
 
   virtual mozilla::SdpMediaSection::MediaType
   GetMediaType() const
   {
     return mType;
   }
 
   virtual const std::string&
@@ -93,17 +115,17 @@ public:
   }
 
   virtual void
   SetCNAME(const std::string& cname)
   {
     mCNAME = cname;
   }
 
-  virtual Direction
+  virtual sdp::Direction
   GetDirection() const
   {
     return mDirection;
   }
 
   virtual const std::vector<uint32_t>&
   GetSsrcs() const
   {
@@ -111,16 +133,27 @@ public:
   }
 
   virtual void
   AddSsrc(uint32_t ssrc)
   {
     mSsrcs.push_back(ssrc);
   }
 
+  virtual void PopulateCodecs(
+      const std::vector<JsepCodecDescription*>& prototype);
+  virtual void AddToOffer(SdpMediaSection* offer) const;
+  virtual void AddToAnswer(const SdpMediaSection& offer,
+                           SdpMediaSection* answer) const;
+  virtual void Negotiate(const SdpMediaSection& answer,
+                         const SdpMediaSection& remote);
+  static void SetUniquePayloadTypes(
+      const std::vector<RefPtr<JsepTrack>>& tracks);
+  virtual void GetNegotiatedPayloadTypes(std::vector<uint16_t>* payloadTypes);
+
   // This will be set when negotiation is carried out.
   virtual const JsepTrackNegotiatedDetails*
   GetNegotiatedDetails() const
   {
     if (mNegotiatedDetails) {
       return mNegotiatedDetails.get();
     }
     return nullptr;
@@ -130,34 +163,56 @@ public:
   GetNegotiatedDetails()
   {
     if (mNegotiatedDetails) {
       return mNegotiatedDetails.get();
     }
     return nullptr;
   }
 
-  // This is for JsepSession's use.
   virtual void
-  SetNegotiatedDetails(UniquePtr<JsepTrackNegotiatedDetails> details)
+  ClearNegotiatedDetails()
   {
-    mNegotiatedDetails = Move(details);
+    mNegotiatedDetails.reset();
   }
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(JsepTrack);
 
 protected:
   virtual ~JsepTrack() {}
 
 private:
+  virtual std::vector<JsepCodecDescription*> GetCodecClones() const;
+  static void EnsureNoDuplicatePayloadTypes(
+      std::vector<JsepCodecDescription*>* codecs);
+  static void GetPayloadTypes(
+      const std::vector<JsepCodecDescription*>& codecs,
+      std::vector<uint16_t>* pts);
+  static void EnsurePayloadTypeIsUnique(std::set<uint16_t>* uniquePayloadTypes,
+                                        JsepCodecDescription* codec);
+  virtual void AddToMsection(const std::vector<JsepCodecDescription*>& codecs,
+                             SdpMediaSection* msection) const;
+
+  // |answer| is set when performing the final negotiation on completion of
+  // offer/answer, and is used to update the formats in |codecs|, since the
+  // answer is authoritative. |formatChanges| is also set on completion of
+  // offer/answer, and records how the formats in |codecs| were changed, which
+  // is used by |Negotiate| to update |mPrototypeCodecs|.
+  virtual void NegotiateCodecs(
+      const SdpMediaSection& remote,
+      std::vector<JsepCodecDescription*>* codecs,
+      const SdpMediaSection* answer = nullptr,
+      std::map<std::string, std::string>* formatChanges = nullptr) const;
+
   const mozilla::SdpMediaSection::MediaType mType;
   std::string mStreamId;
   std::string mTrackId;
   std::string mCNAME;
-  const Direction mDirection;
+  const sdp::Direction mDirection;
+  PtrVector<JsepCodecDescription> mPrototypeCodecs;
   UniquePtr<JsepTrackNegotiatedDetails> mNegotiatedDetails;
   std::vector<uint32_t> mSsrcs;
 };
 
 // Need a better name for this.
 struct JsepTrackPair {
   size_t mLevel;
   // Is this track pair sharing a transport with another?
deleted file mode 100644
--- a/media/webrtc/signaling/src/jsep/JsepTrackImpl.h
+++ /dev/null
@@ -1,92 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef _JSEPTRACKIMPL_H_
-#define _JSEPTRACKIMPL_H_
-
-#include <map>
-
-#include <mozilla/RefPtr.h>
-#include <mozilla/UniquePtr.h>
-
-#include "signaling/src/jsep/JsepCodecDescription.h"
-#include "signaling/src/jsep/JsepTrack.h"
-#include "signaling/src/sdp/Sdp.h"
-#include "signaling/src/sdp/SdpMediaSection.h"
-#include "signaling/src/common/PtrVector.h"
-
-namespace mozilla {
-
-class JsepTrackNegotiatedDetailsImpl : public JsepTrackNegotiatedDetails
-{
-public:
-  virtual ~JsepTrackNegotiatedDetailsImpl()
-  {}
-
-  // Implement JsepTrackNegotiatedDetails.
-  virtual mozilla::SdpMediaSection::Protocol
-  GetProtocol() const override
-  {
-    return mProtocol;
-  }
-  virtual Maybe<std::string>
-  GetBandwidth(const std::string& type) const override
-  {
-    return mBandwidth;
-  }
-  virtual size_t
-  GetCodecCount() const override
-  {
-    return mCodecs.values.size();
-  }
-  virtual nsresult
-  GetCodec(size_t index, const JsepCodecDescription** config) const override
-  {
-    if (index >= mCodecs.values.size()) {
-      return NS_ERROR_INVALID_ARG;
-    }
-    *config = mCodecs.values[index];
-    return NS_OK;
-  }
-
-  virtual const SdpExtmapAttributeList::Extmap*
-  GetExt(const std::string& ext_name) const override
-  {
-    auto it = mExtmap.find(ext_name);
-    if (it != mExtmap.end()) {
-      return &it->second;
-    }
-    return nullptr;
-  }
-
-  virtual std::vector<uint8_t> GetUniquePayloadTypes() const override
-  {
-    return mUniquePayloadTypes;
-  }
-
-  virtual void AddUniquePayloadType(uint8_t pt) override
-  {
-    mUniquePayloadTypes.push_back(pt);
-  }
-
-  virtual void ClearUniquePayloadTypes() override
-  {
-    mUniquePayloadTypes.clear();
-  }
-
-private:
-  // Make these friends to JsepSessionImpl to avoid having to
-  // write setters.
-  friend class JsepSessionImpl;
-
-  mozilla::SdpMediaSection::Protocol mProtocol;
-  Maybe<std::string> mBandwidth;
-  PtrVector<JsepCodecDescription> mCodecs;
-  std::map<std::string, SdpExtmapAttributeList::Extmap> mExtmap;
-  std::vector<uint8_t> mUniquePayloadTypes;
-};
-
-} // namespace mozilla
-
-#endif
--- a/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
+++ b/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
@@ -276,18 +276,17 @@ MediaPipelineFactory::GetTransportParame
         transportLevel, true, *aTrackPair.mRtcpTransport, aRtcpOut);
     if (NS_FAILED(rv)) {
       return rv;
     }
     MOZ_ASSERT(aRtcpOut);
   }
 
   if (aTrackPair.mBundleLevel.isSome()) {
-    bool receiving =
-        aTrack.GetDirection() == JsepTrack::Direction::kJsepTrackReceiving;
+    bool receiving = aTrack.GetDirection() == sdp::kRecv;
 
     *aFilterOut = new MediaPipelineFilter;
 
     if (receiving) {
       // Add remote SSRCs so we can distinguish which RTP packets actually
       // belong to this pipeline (also RTCP sender reports).
       for (auto i = aTrack.GetSsrcs().begin();
           i != aTrack.GetSsrcs().end(); ++i) {
@@ -324,18 +323,17 @@ MediaPipelineFactory::CreateOrUpdateMedi
   // feasible to plumb this information all the way through. So, we set it (for
   // the duration of this call) in a global variable. This allows the GMP code
   // to report errors to the PC.
   WebrtcGmpPCHandleSetter setter(mPC->GetHandle());
 #endif
 
   MOZ_ASSERT(aTrackPair.mRtpTransport);
 
-  bool receiving =
-      aTrack.GetDirection() == JsepTrack::Direction::kJsepTrackReceiving;
+  bool receiving = aTrack.GetDirection() == sdp::kRecv;
 
   size_t level;
   RefPtr<TransportFlow> rtpFlow;
   RefPtr<TransportFlow> rtcpFlow;
   nsAutoPtr<MediaPipelineFilter> filter;
 
   nsresult rv = GetTransportParameters(aTrackPair,
                                        aTrack,
@@ -565,65 +563,67 @@ MediaPipelineFactory::CreateMediaPipelin
     MOZ_MTLOG(ML_ERROR, "Couldn't store receiving pipeline " <<
                         static_cast<unsigned>(rv));
     return rv;
   }
 
   return NS_OK;
 }
 
+static const JsepCodecDescription*
+GetBestCodec(const JsepTrackNegotiatedDetails& details)
+{
+  if (details.GetCodecCount()) {
+    return details.GetCodec(0);
+  }
+  return nullptr;
+}
+
 nsresult
 MediaPipelineFactory::GetOrCreateAudioConduit(
     const JsepTrackPair& aTrackPair,
     const JsepTrack& aTrack,
     RefPtr<MediaSessionConduit>* aConduitp)
 {
 
   if (!aTrack.GetNegotiatedDetails()) {
     MOZ_ASSERT(false, "Track is missing negotiated details");
     return NS_ERROR_INVALID_ARG;
   }
 
-  bool receiving =
-      aTrack.GetDirection() == JsepTrack::Direction::kJsepTrackReceiving;
+  bool receiving = aTrack.GetDirection() == sdp::kRecv;
 
   RefPtr<AudioSessionConduit> conduit =
     mPCMedia->GetAudioConduit(aTrackPair.mLevel);
 
   if (!conduit) {
     conduit = AudioSessionConduit::Create();
     if (!conduit) {
       MOZ_MTLOG(ML_ERROR, "Could not create audio conduit");
       return NS_ERROR_FAILURE;
     }
 
     mPCMedia->AddAudioConduit(aTrackPair.mLevel, conduit);
   }
 
-  size_t numCodecs = aTrack.GetNegotiatedDetails()->GetCodecCount();
-  if (numCodecs == 0) {
+  if (!GetBestCodec(*aTrack.GetNegotiatedDetails())) {
     MOZ_MTLOG(ML_ERROR, "Can't set up a conduit with 0 codecs");
     return NS_ERROR_FAILURE;
   }
 
+  size_t numCodecs = aTrack.GetNegotiatedDetails()->GetCodecCount();
   if (receiving) {
     PtrVector<AudioCodecConfig> configs;
 
     for (size_t i = 0; i < numCodecs; i++) {
-      const JsepCodecDescription* cdesc;
-      nsresult rv = aTrack.GetNegotiatedDetails()->GetCodec(i, &cdesc);
-      MOZ_ASSERT(NS_SUCCEEDED(rv));
-      if (NS_FAILED(rv)) {
-        MOZ_MTLOG(ML_ERROR, "Failed to get codec from jsep track, rv="
-                                << static_cast<uint32_t>(rv));
-        return rv;
-      }
+      const JsepCodecDescription* cdesc =
+        aTrack.GetNegotiatedDetails()->GetCodec(i);
 
       AudioCodecConfig* configRaw;
-      rv = JsepCodecDescToCodecConfig(*cdesc, &configRaw);
+      nsresult rv = JsepCodecDescToCodecConfig(*cdesc, &configRaw);
       if (NS_FAILED(rv))
         return rv;
 
       configs.values.push_back(configRaw);
     }
 
     auto error = conduit->ConfigureRecvMediaCodecs(configs.values);
 
@@ -647,28 +647,21 @@ MediaPipelineFactory::GetOrCreateAudioCo
       if (!conduit->SetLocalSSRC(ssrcs.front())) {
         MOZ_MTLOG(ML_ERROR, "SetLocalSSRC failed");
         return NS_ERROR_FAILURE;
       }
     }
 
     conduit->SetLocalCNAME(aTrack.GetCNAME().c_str());
 
-    const JsepCodecDescription* cdesc;
-    // Best codec.
-    nsresult rv = aTrack.GetNegotiatedDetails()->GetCodec(0, &cdesc);
-    MOZ_ASSERT(NS_SUCCEEDED(rv));
-    if (NS_FAILED(rv)) {
-      MOZ_MTLOG(ML_ERROR, "Failed to get codec from jsep track, rv="
-                              << static_cast<uint32_t>(rv));
-      return rv;
-    }
+    const JsepCodecDescription* cdesc =
+      GetBestCodec(*aTrack.GetNegotiatedDetails());
 
     AudioCodecConfig* configRaw;
-    rv = JsepCodecDescToCodecConfig(*cdesc, &configRaw);
+    nsresult rv = JsepCodecDescToCodecConfig(*cdesc, &configRaw);
     if (NS_FAILED(rv))
       return rv;
 
     ScopedDeletePtr<AudioCodecConfig> config(configRaw);
     auto error = conduit->ConfigureSendMediaCodec(config.get());
     if (error) {
       MOZ_MTLOG(ML_ERROR, "ConfigureSendMediaCodec failed: " << error);
       return NS_ERROR_FAILURE;
@@ -701,62 +694,64 @@ MediaPipelineFactory::GetOrCreateVideoCo
     RefPtr<MediaSessionConduit>* aConduitp)
 {
 
   if (!aTrack.GetNegotiatedDetails()) {
     MOZ_ASSERT(false, "Track is missing negotiated details");
     return NS_ERROR_INVALID_ARG;
   }
 
-  bool receiving =
-      aTrack.GetDirection() == JsepTrack::Direction::kJsepTrackReceiving;
+  bool receiving = aTrack.GetDirection() == sdp::kRecv;
 
   RefPtr<VideoSessionConduit> conduit =
     mPCMedia->GetVideoConduit(aTrackPair.mLevel);
 
   if (!conduit) {
     conduit = VideoSessionConduit::Create();
     if (!conduit) {
-      MOZ_MTLOG(ML_ERROR, "Could not create audio conduit");
+      MOZ_MTLOG(ML_ERROR, "Could not create video conduit");
       return NS_ERROR_FAILURE;
     }
 
     mPCMedia->AddVideoConduit(aTrackPair.mLevel, conduit);
   }
 
-  size_t numCodecs = aTrack.GetNegotiatedDetails()->GetCodecCount();
-  if (numCodecs == 0) {
+  if (!GetBestCodec(*aTrack.GetNegotiatedDetails())) {
     MOZ_MTLOG(ML_ERROR, "Can't set up a conduit with 0 codecs");
     return NS_ERROR_FAILURE;
   }
 
+  size_t numCodecs = aTrack.GetNegotiatedDetails()->GetCodecCount();
+
+  bool configuredH264 = false;
   if (receiving) {
     PtrVector<VideoCodecConfig> configs;
 
     for (size_t i = 0; i < numCodecs; i++) {
-      const JsepCodecDescription* cdesc;
+      const JsepCodecDescription* cdesc =
+        aTrack.GetNegotiatedDetails()->GetCodec(i);
 
-      nsresult rv = aTrack.GetNegotiatedDetails()->GetCodec(i, &cdesc);
-      MOZ_ASSERT(NS_SUCCEEDED(rv));
-      if (NS_FAILED(rv)) {
-        MOZ_MTLOG(ML_ERROR, "Failed to get codec from jsep track, rv="
-                                << static_cast<uint32_t>(rv));
-        return rv;
+      // We can only handle configuring one recv H264 codec
+      if (configuredH264 && (cdesc->mName == "H264")) {
+        continue;
       }
 
       VideoCodecConfig* configRaw;
-      rv = JsepCodecDescToCodecConfig(*cdesc, &configRaw);
+      nsresult rv = JsepCodecDescToCodecConfig(*cdesc, &configRaw);
       if (NS_FAILED(rv))
         return rv;
 
       UniquePtr<VideoCodecConfig> config(configRaw);
       if (EnsureExternalCodec(*conduit, config.get(), false)) {
         continue;
       }
 
+      if (cdesc->mName == "H264") {
+        configuredH264 = true;
+      }
       configs.values.push_back(config.release());
     }
 
     auto error = conduit->ConfigureRecvMediaCodecs(configs.values);
 
     if (error) {
       MOZ_MTLOG(ML_ERROR, "ConfigureRecvMediaCodecs failed: " << error);
       return NS_ERROR_FAILURE;
@@ -777,28 +772,21 @@ MediaPipelineFactory::GetOrCreateVideoCo
       if (!conduit->SetLocalSSRC(ssrcs.front())) {
         MOZ_MTLOG(ML_ERROR, "SetLocalSSRC failed");
         return NS_ERROR_FAILURE;
       }
     }
 
     conduit->SetLocalCNAME(aTrack.GetCNAME().c_str());
 
-    const JsepCodecDescription* cdesc;
-    // Best codec.
-    nsresult rv = aTrack.GetNegotiatedDetails()->GetCodec(0, &cdesc);
-    MOZ_ASSERT(NS_SUCCEEDED(rv));
-    if (NS_FAILED(rv)) {
-      MOZ_MTLOG(ML_ERROR, "Failed to get codec from jsep track, rv="
-                              << static_cast<uint32_t>(rv));
-      return rv;
-    }
+    const JsepCodecDescription* cdesc =
+      GetBestCodec(*aTrack.GetNegotiatedDetails());
 
     VideoCodecConfig* configRaw;
-    rv = JsepCodecDescToCodecConfig(*cdesc, &configRaw);
+    nsresult rv = JsepCodecDescToCodecConfig(*cdesc, &configRaw);
     if (NS_FAILED(rv))
       return rv;
 
     rv = ConfigureVideoCodecMode(aTrack,*conduit);
     if (NS_FAILED(rv)) {
       return rv;
     }
 
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -384,16 +384,17 @@ PeerConnectionImpl::PeerConnectionImpl(c
   , mIsLoop(false)
   , mSTSThread(nullptr)
   , mAllowIceLoopback(false)
   , mAllowIceLinkLocal(false)
   , mMedia(nullptr)
   , mUuidGen(MakeUnique<PCUuidGenerator>())
   , mNumAudioStreams(0)
   , mNumVideoStreams(0)
+  , mHaveConfiguredCodecs(false)
   , mHaveDataStream(false)
   , mAddCandidateErrorCount(0)
   , mTrickle(true) // TODO(ekr@rtfm.com): Use pref
   , mShouldSuppressNegotiationNeeded(false)
 {
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
   MOZ_ASSERT(NS_IsMainThread());
   if (aGlobal) {
@@ -924,16 +925,21 @@ class CompareCodecPriority {
     }
 
   private:
     std::string mPreferredCodec;
 };
 
 nsresult
 PeerConnectionImpl::ConfigureJsepSessionCodecs() {
+  if (mHaveConfiguredCodecs) {
+    return NS_OK;
+  }
+  mHaveConfiguredCodecs = true;
+
 #if !defined(MOZILLA_XPCOMRT_API)
   nsresult res;
   nsCOMPtr<nsIPrefService> prefs =
     do_GetService("@mozilla.org/preferences-service;1", &res);
 
   if (NS_FAILED(res)) {
     CSFLogError(logTag, "%s: Couldn't get prefs service, res=%u",
                         __FUNCTION__,
@@ -1145,25 +1151,18 @@ PeerConnectionImpl::GetDatachannelParame
                             "This is likely to be broken.",
                             __FUNCTION__);
         return NS_ERROR_FAILURE;
       }
 
       for (size_t i = 0;
            i < trackPair.mSending->GetNegotiatedDetails()->GetCodecCount();
            ++i) {
-        const JsepCodecDescription* codec;
-        nsresult res =
-          trackPair.mSending->GetNegotiatedDetails()->GetCodec(i, &codec);
-
-        if (NS_FAILED(res)) {
-          CSFLogError(logTag, "%s: Failed getting codec for m=application.",
-                              __FUNCTION__);
-          continue;
-        }
+        const JsepCodecDescription* codec =
+          trackPair.mSending->GetNegotiatedDetails()->GetCodec(i);
 
         if (codec->mType != SdpMediaSection::kApplication) {
           CSFLogError(logTag, "%s: Codec type for m=application was %u, this "
                               "is a bug.",
                               __FUNCTION__,
                               static_cast<unsigned>(codec->mType));
           MOZ_ASSERT(false, "Codec for m=application was not \"application\"");
           return NS_ERROR_FAILURE;
@@ -1189,16 +1188,73 @@ PeerConnectionImpl::GetDatachannelParame
     }
   }
 
   *datachannelCodec = nullptr;
   *level = 0;
   return NS_OK;
 }
 
+/* static */
+void
+PeerConnectionImpl::DeferredAddTrackToJsepSession(
+    const std::string& pcHandle,
+    SdpMediaSection::MediaType type,
+    const std::string& streamId,
+    const std::string& trackId)
+{
+  PeerConnectionWrapper wrapper(pcHandle);
+
+  if (wrapper.impl()) {
+    if (!PeerConnectionCtx::GetInstance()->isReady()) {
+      MOZ_CRASH("Why is DeferredAddTrackToJsepSession being executed when the "
+                "PeerConnectionCtx isn't ready?");
+    }
+    wrapper.impl()->AddTrackToJsepSession(type, streamId, trackId);
+  }
+}
+
+nsresult
+PeerConnectionImpl::AddTrackToJsepSession(SdpMediaSection::MediaType type,
+                                          const std::string& streamId,
+                                          const std::string& trackId)
+{
+  if (!PeerConnectionCtx::GetInstance()->isReady()) {
+    // We are not ready to configure codecs for this track. We need to defer.
+    PeerConnectionCtx::GetInstance()->queueJSEPOperation(
+        WrapRunnableNM(DeferredAddTrackToJsepSession,
+                       mHandle,
+                       type,
+                       streamId,
+                       trackId));
+    return NS_OK;
+  }
+
+  nsresult res = ConfigureJsepSessionCodecs();
+  if (NS_FAILED(res)) {
+    CSFLogError(logTag, "Failed to configure codecs");
+    return res;
+  }
+
+  res = mJsepSession->AddTrack(
+      new JsepTrack(type, streamId, trackId, sdp::kSend));
+
+  if (NS_FAILED(res)) {
+    std::string errorString = mJsepSession->GetLastError();
+    CSFLogError(logTag, "%s (%s) : pc = %s, error = %s",
+                __FUNCTION__,
+                type == SdpMediaSection::kAudio ? "audio" : "video",
+                mHandle.c_str(),
+                errorString.c_str());
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
 nsresult
 PeerConnectionImpl::InitializeDataChannel()
 {
   PC_AUTO_ENTER_API_CALL_NO_CHECK();
   CSFLogDebug(logTag, "%s", __FUNCTION__);
 
   const JsepApplicationCodecDescription* codec;
   uint16_t level;
@@ -1314,17 +1370,17 @@ PeerConnectionImpl::CreateDataChannel(co
     if (!mUuidGen->Generate(&trackId)) {
       return NS_ERROR_FAILURE;
     }
 
     RefPtr<JsepTrack> track(new JsepTrack(
           mozilla::SdpMediaSection::kApplication,
           streamId,
           trackId,
-          JsepTrack::kJsepTrackSending));
+          sdp::kSend));
 
     rv = mJsepSession->AddTrack(track);
     if (NS_FAILED(rv)) {
       CSFLogError(logTag, "%s: Failed to add application track.",
                           __FUNCTION__);
       return rv;
     }
     mHaveDataStream = true;
@@ -2072,16 +2128,17 @@ PeerConnectionImpl::AddTrack(MediaStream
                              const Sequence<OwningNonNull<DOMMediaStream>>& aStreams)
 {
   PC_AUTO_ENTER_API_CALL(true);
 
   if (!aStreams.Length()) {
     CSFLogError(logTag, "%s: At least one stream arg required", __FUNCTION__);
     return NS_ERROR_FAILURE;
   }
+
   return AddTrack(aTrack, aStreams[0]);
 }
 
 nsresult
 PeerConnectionImpl::AddTrack(MediaStreamTrack& aTrack,
                              DOMMediaStream& aMediaStream)
 {
   if (!aMediaStream.HasTrack(aTrack)) {
@@ -2100,49 +2157,35 @@ PeerConnectionImpl::AddTrack(MediaStream
   CSFLogDebug(logTag, "Added track (%s) to stream %s",
                       trackId.c_str(), streamId.c_str());
 
   if (num != mMedia->LocalStreamsLength()) {
     aMediaStream.AddPrincipalChangeObserver(this);
   }
 
   if (aTrack.AsAudioStreamTrack()) {
-    res = mJsepSession->AddTrack(new JsepTrack(
-        mozilla::SdpMediaSection::kAudio,
-        streamId,
-        trackId,
-        JsepTrack::kJsepTrackSending));
+    res = AddTrackToJsepSession(SdpMediaSection::kAudio, streamId, trackId);
     if (NS_FAILED(res)) {
-      std::string errorString = mJsepSession->GetLastError();
-      CSFLogError(logTag, "%s (audio) : pc = %s, error = %s",
-                  __FUNCTION__, mHandle.c_str(), errorString.c_str());
-      return NS_ERROR_FAILURE;
+      return res;
     }
     mNumAudioStreams++;
   }
 
   if (aTrack.AsVideoStreamTrack()) {
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
     if (!Preferences::GetBool("media.peerconnection.video.enabled", true)) {
       // Before this code was moved, this would silently ignore just like it
       // does now. Is this actually what we want to do?
       return NS_OK;
     }
 #endif
 
-    res = mJsepSession->AddTrack(new JsepTrack(
-        mozilla::SdpMediaSection::kVideo,
-        streamId,
-        trackId,
-        JsepTrack::kJsepTrackSending));
+    res = AddTrackToJsepSession(SdpMediaSection::kVideo, streamId, trackId);
     if (NS_FAILED(res)) {
-      std::string errorString = mJsepSession->GetLastError();
-      CSFLogError(logTag, "%s (video) : pc = %s, error = %s",
-                  __FUNCTION__, mHandle.c_str(), errorString.c_str());
-      return NS_ERROR_FAILURE;
+      return res;
     }
     mNumVideoStreams++;
   }
   OnNegotiationNeeded();
   return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
@@ -668,16 +668,25 @@ private:
   void SendLocalIceCandidateToContent(uint16_t level,
                                       const std::string& mid,
                                       const std::string& candidate);
 
   nsresult GetDatachannelParameters(
       const mozilla::JsepApplicationCodecDescription** codec,
       uint16_t* level) const;
 
+  static void DeferredAddTrackToJsepSession(const std::string& pcHandle,
+                                            SdpMediaSection::MediaType type,
+                                            const std::string& streamId,
+                                            const std::string& trackId);
+
+  nsresult AddTrackToJsepSession(SdpMediaSection::MediaType type,
+                                 const std::string& streamId,
+                                 const std::string& trackId);
+
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
   static void GetStatsForPCObserver_s(
       const std::string& pcHandle,
       nsAutoPtr<RTCStatsQuery> query);
 
   // Sends an RTCStatsReport to JS. Must run on main thread.
   static void DeliverStatsReportToPCObserver_m(
       const std::string& pcHandle,
@@ -772,16 +781,17 @@ private:
 #endif
 
   // Temporary: used to prevent multiple audio streams or multiple video streams
   // in a single PC. This is tied up in the IETF discussion around proper
   // representation of multiple streams in SDP, and strongly related to
   // Bug 840728.
   int mNumAudioStreams;
   int mNumVideoStreams;
+  bool mHaveConfiguredCodecs;
 
   bool mHaveDataStream;
 
   unsigned int mAddCandidateErrorCount;
 
   bool mTrickle;
 
   bool mShouldSuppressNegotiationNeeded;
--- a/media/webrtc/signaling/src/sdp/SdpAttribute.cpp
+++ b/media/webrtc/signaling/src/sdp/SdpAttribute.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "signaling/src/sdp/SdpAttribute.h"
+#include "signaling/src/sdp/SdpHelper.h"
 
 #include <iomanip>
 
 #ifdef CRLF
 #undef CRLF
 #endif
 #define CRLF "\r\n"
 
@@ -937,16 +938,39 @@ SdpSimulcastAttribute::Version::Parse(st
     }
     choices.push_back(value);
   } while (SkipChar(is, ',', error));
 
   return true;
 }
 
 void
+SdpSimulcastAttribute::Version::AppendAsStrings(
+    std::vector<std::string>* formats) const
+{
+  for (uint16_t pt : choices) {
+    std::ostringstream os;
+    os << pt;
+    formats->push_back(os.str());
+  }
+}
+
+void
+SdpSimulcastAttribute::Version::AddChoice(const std::string& pt)
+{
+  uint16_t ptAsInt;
+  if (!SdpHelper::GetPtAsInt(pt, &ptAsInt)) {
+    MOZ_ASSERT(false);
+    return;
+  }
+
+  choices.push_back(ptAsInt);
+}
+
+void
 SdpSimulcastAttribute::Versions::Serialize(std::ostream& os) const
 {
   bool first = true;
   for (const Version& version : *this) {
     if (!version.IsSet()) {
       continue;
     }
 
--- a/media/webrtc/signaling/src/sdp/SdpAttribute.h
+++ b/media/webrtc/signaling/src/sdp/SdpAttribute.h
@@ -173,24 +173,21 @@ inline std::ostream& operator<<(std::ost
 }
 
 ///////////////////////////////////////////////////////////////////////////
 // a=sendrecv / a=sendonly / a=recvonly / a=inactive, RFC 4566
 //-------------------------------------------------------------------------
 class SdpDirectionAttribute : public SdpAttribute
 {
 public:
-  static const unsigned kSendFlag = 1;
-  static const unsigned kRecvFlag = 1 << 1;
-
   enum Direction {
     kInactive = 0,
-    kSendonly = kSendFlag,
-    kRecvonly = kRecvFlag,
-    kSendrecv = kSendFlag | kRecvFlag
+    kSendonly = sdp::kSend,
+    kRecvonly = sdp::kRecv,
+    kSendrecv = sdp::kSend | sdp::kRecv
   };
 
   explicit SdpDirectionAttribute(Direction value)
       : SdpAttribute(kDirectionAttribute), mValue(value)
   {
   }
 
   virtual void Serialize(std::ostream& os) const override;
@@ -1148,16 +1145,24 @@ public:
     Fmtp(const std::string& aFormat, const std::string& aParametersString,
          UniquePtr<Parameters> aParameters)
         : format(aFormat),
           parameters_string(aParametersString),
           parameters(Move(aParameters))
     {
     }
 
+    Fmtp(const std::string& aFormat, const std::string& aParametersString,
+         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;
@@ -1318,16 +1323,18 @@ public:
   {
     public:
       void Serialize(std::ostream& os) const;
       bool IsSet() const
       {
         return !choices.empty();
       }
       bool Parse(std::istream& is, std::string* error);
+      void AppendAsStrings(std::vector<std::string>* formats) const;
+      void AddChoice(const std::string& pt);
 
       std::vector<uint16_t> choices;
   };
 
   class Versions : public std::vector<Version>
   {
     public:
       void Serialize(std::ostream& os) const;
--- a/media/webrtc/signaling/src/sdp/SdpEnum.h
+++ b/media/webrtc/signaling/src/sdp/SdpEnum.h
@@ -41,13 +41,19 @@ inline std::ostream& operator<<(std::ost
     case sdp::kIPv4:
       return os << "IP4";
     case sdp::kIPv6:
       return os << "IP6";
   }
   MOZ_CRASH("Unknown AddrType");
 }
 
+enum Direction {
+  // Start at 1 so these can be used as flags
+  kSend = 1,
+  kRecv = 2
+};
+
 } // namespace sdp
 
 } // namespace mozilla
 
 #endif
--- a/media/webrtc/signaling/src/sdp/SdpHelper.cpp
+++ b/media/webrtc/signaling/src/sdp/SdpHelper.cpp
@@ -113,17 +113,17 @@ bool
 SdpHelper::MsectionIsDisabled(const SdpMediaSection& msection) const
 {
   return !msection.GetPort() &&
          !msection.GetAttributeList().HasAttribute(
              SdpAttribute::kBundleOnlyAttribute);
 }
 
 void
-SdpHelper::DisableMsection(Sdp* sdp, SdpMediaSection* msection) const
+SdpHelper::DisableMsection(Sdp* sdp, SdpMediaSection* msection)
 {
   // Make sure to remove the mid from any group attributes
   if (msection->GetAttributeList().HasAttribute(SdpAttribute::kMidAttribute)) {
     std::string mid = msection->GetAttributeList().GetMid();
     if (sdp->GetAttributeList().HasAttribute(SdpAttribute::kGroupAttribute)) {
       UniquePtr<SdpGroupAttributeList> newGroupAttr(new SdpGroupAttributeList(
             sdp->GetAttributeList().GetGroup()));
       newGroupAttr->RemoveMid(mid);
@@ -549,38 +549,16 @@ SdpHelper::FindMsectionByMid(Sdp& sdp,
     if (attrs.HasAttribute(SdpAttribute::kMidAttribute) &&
         attrs.GetMid() == mid) {
       return &sdp.GetMediaSection(i);
     }
   }
   return nullptr;
 }
 
-void
-SdpHelper::SetSsrcs(const std::vector<uint32_t>& ssrcs,
-                    const std::string& cname,
-                    SdpMediaSection* msection) const
-{
-  if (ssrcs.empty()) {
-    msection->GetAttributeList().RemoveAttribute(SdpAttribute::kSsrcAttribute);
-    return;
-  }
-
-  UniquePtr<SdpSsrcAttributeList> ssrcAttr(new SdpSsrcAttributeList);
-  for (auto ssrc : ssrcs) {
-    // When using ssrc attributes, we are required to at least have a cname.
-    // (See https://tools.ietf.org/html/rfc5576#section-6.1)
-    std::string cnameAttr("cname:");
-    cnameAttr += cname;
-    ssrcAttr->PushEntry(ssrc, cnameAttr);
-  }
-
-  msection->GetAttributeList().SetAttribute(ssrcAttr.release());
-}
-
 nsresult
 SdpHelper::CopyStickyParams(const SdpMediaSection& source,
                             SdpMediaSection* dest)
 {
   auto& sourceAttrs = source.GetAttributeList();
   auto& destAttrs = dest->GetAttributeList();
 
   // There's no reason to renegotiate rtcp-mux
--- a/media/webrtc/signaling/src/sdp/SdpHelper.h
+++ b/media/webrtc/signaling/src/sdp/SdpHelper.h
@@ -31,17 +31,17 @@ class SdpHelper {
     nsresult CopyTransportParams(size_t numComponents,
                                  const SdpMediaSection& source,
                                  SdpMediaSection* dest);
     bool AreOldTransportParamsValid(const Sdp& oldAnswer,
                                     const Sdp& newOffer,
                                     size_t level);
 
     bool MsectionIsDisabled(const SdpMediaSection& msection) const;
-    void DisableMsection(Sdp* sdp, SdpMediaSection* msection) const;
+    static void DisableMsection(Sdp* sdp, SdpMediaSection* msection);
 
     // Maps each mid to the m-section that is the master of its bundle.
     // Mids that do not appear in an a=group:BUNDLE do not appear here.
     typedef std::map<std::string, const SdpMediaSection*> BundledMids;
 
     nsresult GetBundledMids(const Sdp& sdp, BundledMids* bundledMids);
 
     bool IsBundleSlave(const Sdp& localSdp, uint16_t level);
@@ -76,19 +76,16 @@ class SdpHelper {
                              uint16_t defaultCandidatePort,
                              const std::string& defaultRtcpCandidateAddr,
                              uint16_t defaultRtcpCandidatePort,
                              SdpMediaSection* msection);
     void SetupMsidSemantic(const std::vector<std::string>& msids,
                            Sdp* sdp) const;
 
     std::string GetCNAME(const SdpMediaSection& msection) const;
-    void SetSsrcs(const std::vector<uint32_t>& ssrcs,
-                  const std::string& cname,
-                  SdpMediaSection* msection) const;
 
     SdpMediaSection* FindMsectionByMid(Sdp& sdp,
                                        const std::string& mid) const;
 
     const SdpMediaSection* FindMsectionByMid(const Sdp& sdp,
                                              const std::string& mid) const;
 
     nsresult CopyStickyParams(const SdpMediaSection& source,
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/sdp/SdpMediaSection.cpp
@@ -0,0 +1,160 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "signaling/src/sdp/SdpMediaSection.h"
+
+namespace mozilla
+{
+const SdpFmtpAttributeList::Parameters*
+SdpMediaSection::FindFmtp(const std::string& pt) const
+{
+  const SdpAttributeList& attrs = GetAttributeList();
+
+  if (attrs.HasAttribute(SdpAttribute::kFmtpAttribute)) {
+    for (auto& fmtpAttr : attrs.GetFmtp().mFmtps) {
+      if (fmtpAttr.format == pt && fmtpAttr.parameters) {
+        return fmtpAttr.parameters.get();
+      }
+    }
+  }
+  return nullptr;
+}
+
+void
+SdpMediaSection::SetFmtp(const SdpFmtpAttributeList::Fmtp& fmtpToSet)
+{
+  UniquePtr<SdpFmtpAttributeList> fmtps(new SdpFmtpAttributeList);
+
+  if (GetAttributeList().HasAttribute(SdpAttribute::kFmtpAttribute)) {
+    *fmtps = GetAttributeList().GetFmtp();
+  }
+
+  bool found = false;
+  for (SdpFmtpAttributeList::Fmtp& fmtp : fmtps->mFmtps) {
+    if (fmtp.format == fmtpToSet.format) {
+      fmtp = fmtpToSet;
+      found = true;
+    }
+  }
+
+  if (!found) {
+    fmtps->mFmtps.push_back(fmtpToSet);
+  }
+
+  GetAttributeList().SetAttribute(fmtps.release());
+}
+
+const SdpRtpmapAttributeList::Rtpmap*
+SdpMediaSection::FindRtpmap(const std::string& pt) const
+{
+  auto& attrs = GetAttributeList();
+  if (!attrs.HasAttribute(SdpAttribute::kRtpmapAttribute)) {
+    return nullptr;
+  }
+
+  const SdpRtpmapAttributeList& rtpmap = attrs.GetRtpmap();
+  if (!rtpmap.HasEntry(pt)) {
+    return nullptr;
+  }
+
+  return &rtpmap.GetEntry(pt);
+}
+
+const SdpSctpmapAttributeList::Sctpmap*
+SdpMediaSection::FindSctpmap(const std::string& pt) const
+{
+  auto& attrs = GetAttributeList();
+  if (!attrs.HasAttribute(SdpAttribute::kSctpmapAttribute)) {
+    return nullptr;
+  }
+
+  const SdpSctpmapAttributeList& sctpmap = attrs.GetSctpmap();
+  if (!sctpmap.HasEntry(pt)) {
+    return nullptr;
+  }
+
+  return &sctpmap.GetEntry(pt);
+}
+
+bool
+SdpMediaSection::HasRtcpFb(const std::string& pt,
+                           SdpRtcpFbAttributeList::Type type,
+                           const std::string& subType) const
+{
+  const SdpAttributeList& attrs(GetAttributeList());
+
+  if (!attrs.HasAttribute(SdpAttribute::kRtcpFbAttribute)) {
+    return false;
+  }
+
+  for (auto& rtcpfb : attrs.GetRtcpFb().mFeedbacks) {
+    if (rtcpfb.type == type) {
+      if (rtcpfb.pt == "*" || rtcpfb.pt == pt) {
+        if (rtcpfb.parameter == subType) {
+          return true;
+        }
+      }
+    }
+  }
+
+  return false;
+}
+
+SdpRtcpFbAttributeList
+SdpMediaSection::GetRtcpFbs() const
+{
+  SdpRtcpFbAttributeList result;
+  if (GetAttributeList().HasAttribute(SdpAttribute::kRtcpFbAttribute)) {
+    result = GetAttributeList().GetRtcpFb();
+  }
+  return result;
+}
+
+void
+SdpMediaSection::SetRtcpFbs(const SdpRtcpFbAttributeList& rtcpfbs)
+{
+  if (rtcpfbs.mFeedbacks.empty()) {
+    GetAttributeList().RemoveAttribute(SdpAttribute::kRtcpFbAttribute);
+    return;
+  }
+
+  GetAttributeList().SetAttribute(new SdpRtcpFbAttributeList(rtcpfbs));
+}
+
+void
+SdpMediaSection::SetSsrcs(const std::vector<uint32_t>& ssrcs,
+                          const std::string& cname)
+{
+  if (ssrcs.empty()) {
+    GetAttributeList().RemoveAttribute(SdpAttribute::kSsrcAttribute);
+    return;
+  }
+
+  UniquePtr<SdpSsrcAttributeList> ssrcAttr(new SdpSsrcAttributeList);
+  for (auto ssrc : ssrcs) {
+    // When using ssrc attributes, we are required to at least have a cname.
+    // (See https://tools.ietf.org/html/rfc5576#section-6.1)
+    std::string cnameAttr("cname:");
+    cnameAttr += cname;
+    ssrcAttr->PushEntry(ssrc, cnameAttr);
+  }
+
+  GetAttributeList().SetAttribute(ssrcAttr.release());
+}
+
+void
+SdpMediaSection::AddMsid(const std::string& id, const std::string& appdata)
+{
+  UniquePtr<SdpMsidAttributeList> msids(new SdpMsidAttributeList);
+  if (GetAttributeList().HasAttribute(SdpAttribute::kMsidAttribute)) {
+    msids->mMsids = GetAttributeList().GetMsid().mMsids;
+  }
+  msids->PushEntry(id, appdata);
+  GetAttributeList().SetAttribute(msids.release());
+}
+
+} // namespace mozilla
+
--- a/media/webrtc/signaling/src/sdp/SdpMediaSection.h
+++ b/media/webrtc/signaling/src/sdp/SdpMediaSection.h
@@ -7,18 +7,17 @@
 #ifndef _SDPMEDIASECTION_H_
 #define _SDPMEDIASECTION_H_
 
 #include "mozilla/Maybe.h"
 #include "signaling/src/sdp/SdpEnum.h"
 #include "signaling/src/sdp/SdpAttributeList.h"
 #include <string>
 #include <vector>
-
-#include "signaling/src/sdp/SdpEnum.h"
+#include <iostream>
 
 namespace mozilla
 {
 
 class SdpAttributeList;
 
 class SdpConnection;
 
@@ -76,16 +75,18 @@ public:
   virtual void SetPort(unsigned int port) = 0;
   virtual unsigned int GetPortCount() const = 0;
   virtual Protocol GetProtocol() const = 0;
   virtual const SdpConnection& GetConnection() const = 0;
   virtual SdpConnection& GetConnection() = 0;
   virtual uint32_t GetBandwidth(const std::string& type) const = 0;
   virtual const std::vector<std::string>& GetFormats() const = 0;
 
+  std::vector<std::string> GetFormatsForSimulcastVersion(
+      size_t simulcastVersion, bool send, bool recv) const;
   virtual const SdpAttributeList& GetAttributeList() const = 0;
   virtual SdpAttributeList& GetAttributeList() = 0;
 
   virtual SdpDirectionAttribute GetDirectionAttribute() const = 0;
 
   virtual void Serialize(std::ostream&) const = 0;
 
   virtual void AddCodec(const std::string& pt, const std::string& name,
@@ -99,133 +100,78 @@ public:
   GetLevel() const
   {
     return mLevel;
   }
 
   inline bool
   IsReceiving() const
   {
-    return GetDirectionAttribute().mValue & SdpDirectionAttribute::kRecvFlag;
+    return GetDirectionAttribute().mValue & sdp::kRecv;
   }
 
   inline bool
   IsSending() const
   {
-    return GetDirectionAttribute().mValue & SdpDirectionAttribute::kSendFlag;
+    return GetDirectionAttribute().mValue & sdp::kSend;
   }
 
   inline void
   SetReceiving(bool receiving)
   {
     auto direction = GetDirectionAttribute().mValue;
-    if (direction & SdpDirectionAttribute::kSendFlag) {
+    if (direction & sdp::kSend) {
       SetDirection(receiving ?
                    SdpDirectionAttribute::kSendrecv :
                    SdpDirectionAttribute::kSendonly);
     } else {
       SetDirection(receiving ?
                    SdpDirectionAttribute::kRecvonly :
                    SdpDirectionAttribute::kInactive);
     }
   }
 
   inline void
   SetSending(bool sending)
   {
     auto direction = GetDirectionAttribute().mValue;
-    if (direction & SdpDirectionAttribute::kRecvFlag) {
+    if (direction & sdp::kRecv) {
       SetDirection(sending ?
                    SdpDirectionAttribute::kSendrecv :
                    SdpDirectionAttribute::kRecvonly);
     } else {
       SetDirection(sending ?
                    SdpDirectionAttribute::kSendonly :
                    SdpDirectionAttribute::kInactive);
     }
   }
 
   inline void SetDirection(SdpDirectionAttribute::Direction direction)
   {
     GetAttributeList().SetAttribute(new SdpDirectionAttribute(direction));
   }
 
-  const SdpFmtpAttributeList::Parameters*
-  FindFmtp(const std::string& pt) const
-  {
-    const SdpAttributeList& attrs = GetAttributeList();
-
-    if (attrs.HasAttribute(SdpAttribute::kFmtpAttribute)) {
-      const SdpFmtpAttributeList& fmtps = attrs.GetFmtp();
-      for (auto i = fmtps.mFmtps.begin(); i != fmtps.mFmtps.end(); ++i) {
-        if (i->format == pt && i->parameters) {
-          return i->parameters.get();
-        }
-      }
-    }
-    return nullptr;
-  }
-
-  const SdpRtpmapAttributeList::Rtpmap*
-  FindRtpmap(const std::string& pt) const
-  {
-    auto& attrs = GetAttributeList();
-    if (!attrs.HasAttribute(SdpAttribute::kRtpmapAttribute)) {
-      return nullptr;
-    }
-
-    const SdpRtpmapAttributeList& rtpmap = attrs.GetRtpmap();
-    if (!rtpmap.HasEntry(pt)) {
-      return nullptr;
-    }
-
-    return &rtpmap.GetEntry(pt);
-  }
-
-  const SdpSctpmapAttributeList::Sctpmap*
-  FindSctpmap(const std::string& pt) const
+  const SdpFmtpAttributeList::Parameters* FindFmtp(const std::string& pt) const;
+  void SetFmtp(const SdpFmtpAttributeList::Fmtp& fmtp);
+  const SdpRtpmapAttributeList::Rtpmap* FindRtpmap(const std::string& pt) const;
+  const SdpSctpmapAttributeList::Sctpmap* FindSctpmap(
+      const std::string& pt) const;
+  bool HasRtcpFb(const std::string& pt,
+                 SdpRtcpFbAttributeList::Type type,
+                 const std::string& subType) const;
+  SdpRtcpFbAttributeList GetRtcpFbs() const;
+  void SetRtcpFbs(const SdpRtcpFbAttributeList& rtcpfbs);
+  bool HasFormat(const std::string& format) const
   {
-    auto& attrs = GetAttributeList();
-    if (!attrs.HasAttribute(SdpAttribute::kSctpmapAttribute)) {
-      return nullptr;
-    }
-
-    const SdpSctpmapAttributeList& sctpmap = attrs.GetSctpmap();
-    if (!sctpmap.HasEntry(pt)) {
-      return nullptr;
-    }
-
-    return &sctpmap.GetEntry(pt);
+    return std::find(GetFormats().begin(), GetFormats().end(), format) !=
+        GetFormats().end();
   }
-
-  bool
-  HasRtcpFb(const std::string& pt,
-            SdpRtcpFbAttributeList::Type type,
-            const std::string& subType) const
-  {
-    const SdpAttributeList& attrs(GetAttributeList());
-
-    if (!attrs.HasAttribute(SdpAttribute::kRtcpFbAttribute)) {
-      return false;
-    }
-
-    for (auto& rtcpfb : attrs.GetRtcpFb().mFeedbacks) {
-      if (rtcpfb.type == type) {
-        if (rtcpfb.pt == "*" || rtcpfb.pt == pt) {
-          if (rtcpfb.parameter == subType) {
-            return true;
-          }
-        }
-      }
-    }
-
-    return false;
-  }
-
-
+  void SetSsrcs(const std::vector<uint32_t>& ssrcs,
+                const std::string& cname);
+  void AddMsid(const std::string& id, const std::string& appdata);
 
 private:
   size_t mLevel;
 };
 
 inline std::ostream& operator<<(std::ostream& os, const SdpMediaSection& ms)
 {
   ms.Serialize(os);
--- a/media/webrtc/signaling/src/sdp/SipccSdpMediaSection.cpp
+++ b/media/webrtc/signaling/src/sdp/SipccSdpMediaSection.cpp
@@ -122,17 +122,20 @@ SipccSdpMediaSection::Load(sdp_t* sdp, u
     return false;
   } else {
     mPortCount = pc;
   }
 
   if (!LoadProtocol(sdp, level, errorHolder)) {
     return false;
   }
-  LoadFormats(sdp, level);
+
+  if (!LoadFormats(sdp, level, errorHolder)) {
+    return false;
+  }
 
   if (!mAttributeList.Load(sdp, level, errorHolder)) {
     return false;
   }
 
   if (!mBandwidths.Load(sdp, level, errorHolder)) {
     return false;
   }
@@ -176,43 +179,53 @@ SipccSdpMediaSection::LoadProtocol(sdp_t
     default:
       errorHolder.AddParseError(sdp_get_media_line_number(sdp, level),
                                 "Unsupported media transport type");
       return false;
   }
   return true;
 }
 
-void
-SipccSdpMediaSection::LoadFormats(sdp_t* sdp, uint16_t level)
+bool
+SipccSdpMediaSection::LoadFormats(sdp_t* sdp,
+                                  uint16_t level,
+                                  SdpErrorHolder& errorHolder)
 {
   sdp_media_e mtype = sdp_get_media_type(sdp, level);
 
   if (mtype == SDP_MEDIA_APPLICATION) {
     uint32_t ptype = sdp_get_media_sctp_port(sdp, level);
     std::ostringstream osPayloadType;
     osPayloadType << ptype;
     mFormats.push_back(osPayloadType.str());
   } else if (mtype == SDP_MEDIA_AUDIO || mtype == SDP_MEDIA_VIDEO) {
     uint16_t count = sdp_get_media_num_payload_types(sdp, level);
     for (uint16_t i = 0; i < count; ++i) {
       sdp_payload_ind_e indicator; // we ignore this, which is fine
       uint32_t ptype =
           sdp_get_media_payload_type(sdp, level, i + 1, &indicator);
 
+      if (GET_DYN_PAYLOAD_TYPE_VALUE(ptype) > UINT8_MAX) {
+        errorHolder.AddParseError(sdp_get_media_line_number(sdp, level),
+                                  "Format is too large");
+        return false;
+      }
+
       std::ostringstream osPayloadType;
       // sipcc stores payload types in a funny way. When sipcc and the SDP it
       // parsed differ on what payload type number should be used for a given
       // codec, sipcc's value goes in the lower byte, and the SDP's value in
       // the upper byte. When they do not differ, only the lower byte is used.
       // We want what was in the SDP, verbatim.
       osPayloadType << GET_DYN_PAYLOAD_TYPE_VALUE(ptype);
       mFormats.push_back(osPayloadType.str());
     }
   }
+
+  return true;
 }
 
 bool
 SipccSdpMediaSection::LoadConnection(sdp_t* sdp, uint16_t level,
                                      SdpErrorHolder& errorHolder)
 {
   if (!sdp_connection_valid(sdp, level)) {
     level = SDP_SESSION_LEVEL;
--- a/media/webrtc/signaling/src/sdp/SipccSdpMediaSection.h
+++ b/media/webrtc/signaling/src/sdp/SipccSdpMediaSection.h
@@ -70,17 +70,17 @@ private:
   SipccSdpMediaSection(size_t level, const SipccSdpAttributeList* sessionLevel)
       : SdpMediaSection(level), mAttributeList(sessionLevel)
   {
   }
 
   bool Load(sdp_t* sdp, uint16_t level, SdpErrorHolder& errorHolder);
   bool LoadConnection(sdp_t* sdp, uint16_t level, SdpErrorHolder& errorHolder);
   bool LoadProtocol(sdp_t* sdp, uint16_t level, SdpErrorHolder& errorHolder);
-  void LoadFormats(sdp_t* sdp, uint16_t level);
+  bool LoadFormats(sdp_t* sdp, uint16_t level, SdpErrorHolder& errorHolder);
 
   // the following values are cached on first get
   MediaType mMediaType;
   uint16_t mPort;
   uint16_t mPortCount;
   Protocol mProtocol;
   std::vector<std::string> mFormats;
 
--- a/media/webrtc/signaling/test/jsep_session_unittest.cpp
+++ b/media/webrtc/signaling/test/jsep_session_unittest.cpp
@@ -833,46 +833,29 @@ protected:
     (*sdp)[pos + 11] = 'G'; // garble, a=group:BUNGLE
   }
 
   void
   DisableMsection(std::string* sdp, size_t level) const {
     UniquePtr<Sdp> parsed(Parse(*sdp));
     ASSERT_TRUE(parsed.get());
     ASSERT_LT(level, parsed->GetMediaSectionCount());
-    parsed->GetMediaSection(level).SetPort(0);
-
-    auto& attrs = parsed->GetMediaSection(level).GetAttributeList();
-
-    ASSERT_TRUE(attrs.HasAttribute(SdpAttribute::kMidAttribute));
-    std::string mid = attrs.GetMid();
-
-    attrs.Clear();
-
-    ASSERT_TRUE(
-        parsed->GetAttributeList().HasAttribute(SdpAttribute::kGroupAttribute));
-
-    SdpGroupAttributeList* newGroupAttr(new SdpGroupAttributeList(
-          parsed->GetAttributeList().GetGroup()));
-    newGroupAttr->RemoveMid(mid);
-    parsed->GetAttributeList().SetAttribute(newGroupAttr);
+    SdpHelper::DisableMsection(parsed.get(), &parsed->GetMediaSection(level));
     (*sdp) = parsed->ToString();
   }
 
   void
   DumpTrack(const JsepTrack& track)
   {
     std::cerr << "  type=" << track.GetMediaType() << std::endl;
-    std::cerr << "  protocol=" << track.GetNegotiatedDetails()->GetProtocol()
-              << std::endl;
     std::cerr << "  codecs=" << std::endl;
     size_t num_codecs = track.GetNegotiatedDetails()->GetCodecCount();
     for (size_t i = 0; i < num_codecs; ++i) {
-      const JsepCodecDescription* codec;
-      ASSERT_EQ(NS_OK, track.GetNegotiatedDetails()->GetCodec(i, &codec));
+      const JsepCodecDescription* codec =
+        track.GetNegotiatedDetails()->GetCodec(i);
       std::cerr << "    " << codec->mName << std::endl;
     }
   }
 
   void
   DumpTrackPairs(const JsepSessionImpl& session)
   {
     auto pairs = mSessionAns.GetNegotiatedTrackPairs();
@@ -2647,60 +2630,60 @@ TEST_F(JsepSessionTest, ValidateOfferedC
 
   // Validate fmtps
   ASSERT_TRUE(video_attrs.HasAttribute(SdpAttribute::kFmtpAttribute));
   auto& fmtps = video_attrs.GetFmtp().mFmtps;
 
   ASSERT_EQ(4U, fmtps.size());
 
   // VP8
-  ASSERT_EQ("120", fmtps[0].format);
-  ASSERT_TRUE(!!fmtps[0].parameters);
-  ASSERT_EQ(SdpRtpmapAttributeList::kVP8, fmtps[0].parameters->codec_type);
+  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 =
-      *static_cast<const SdpFmtpAttributeList::VP8Parameters*>(
-          fmtps[0].parameters.get());
+      *static_cast<const SdpFmtpAttributeList::VP8Parameters*>(vp8_params);
 
   ASSERT_EQ((uint32_t)12288, parsed_vp8_params.max_fs);
   ASSERT_EQ((uint32_t)60, parsed_vp8_params.max_fr);
 
   // VP9
-  ASSERT_EQ("121", fmtps[1].format);
-  ASSERT_TRUE(!!fmtps[1].parameters);
-  ASSERT_EQ(SdpRtpmapAttributeList::kVP9, fmtps[1].parameters->codec_type);
+  const SdpFmtpAttributeList::Parameters* vp9_params =
+    video_section.FindFmtp("121");
+  ASSERT_TRUE(vp9_params);
+  ASSERT_EQ(SdpRtpmapAttributeList::kVP9, vp9_params->codec_type);
 
   auto& parsed_vp9_params =
-      *static_cast<const SdpFmtpAttributeList::VP8Parameters*>(
-          fmtps[1].parameters.get());
+      *static_cast<const SdpFmtpAttributeList::VP8Parameters*>(vp9_params);
 
   ASSERT_EQ((uint32_t)12288, parsed_vp9_params.max_fs);
   ASSERT_EQ((uint32_t)60, parsed_vp9_params.max_fr);
 
   // H264 packetization mode 1
-  ASSERT_EQ("126", fmtps[2].format);
-  ASSERT_TRUE(!!fmtps[2].parameters);
-  ASSERT_EQ(SdpRtpmapAttributeList::kH264, fmtps[2].parameters->codec_type);
+  const SdpFmtpAttributeList::Parameters* h264_1_params =
+    video_section.FindFmtp("126");
+  ASSERT_TRUE(h264_1_params);
+  ASSERT_EQ(SdpRtpmapAttributeList::kH264, h264_1_params->codec_type);
 
   auto& parsed_h264_1_params =
-      *static_cast<const SdpFmtpAttributeList::H264Parameters*>(
-          fmtps[2].parameters.get());
+      *static_cast<const SdpFmtpAttributeList::H264Parameters*>(h264_1_params);
 
   ASSERT_EQ((uint32_t)0x42e00d, parsed_h264_1_params.profile_level_id);
   ASSERT_TRUE(parsed_h264_1_params.level_asymmetry_allowed);
   ASSERT_EQ(1U, parsed_h264_1_params.packetization_mode);
 
   // H264 packetization mode 0
-  ASSERT_EQ("97", fmtps[3].format);
-  ASSERT_TRUE(!!fmtps[3].parameters);
-  ASSERT_EQ(SdpRtpmapAttributeList::kH264, fmtps[3].parameters->codec_type);
+  const SdpFmtpAttributeList::Parameters* h264_0_params =
+    video_section.FindFmtp("97");
+  ASSERT_TRUE(h264_0_params);
+  ASSERT_EQ(SdpRtpmapAttributeList::kH264, h264_0_params->codec_type);
 
   auto& parsed_h264_0_params =
-      *static_cast<const SdpFmtpAttributeList::H264Parameters*>(
-          fmtps[3].parameters.get());
+      *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);
 }
 
 TEST_F(JsepSessionTest, ValidateAnsweredCodecParams)
 {
@@ -2803,47 +2786,27 @@ TEST_F(JsepSessionTest, ValidateAnswered
   ASSERT_TRUE(offerPairs[1].mSending);
   ASSERT_TRUE(offerPairs[1].mReceiving);
   ASSERT_TRUE(offerPairs[1].mSending->GetNegotiatedDetails());
   ASSERT_TRUE(offerPairs[1].mReceiving->GetNegotiatedDetails());
   ASSERT_EQ(1U,
       offerPairs[1].mSending->GetNegotiatedDetails()->GetCodecCount());
   ASSERT_EQ(1U,
       offerPairs[1].mReceiving->GetNegotiatedDetails()->GetCodecCount());
-  const JsepCodecDescription* offerRecvCodec;
-  ASSERT_EQ(NS_OK,
-      offerPairs[1].mReceiving->GetNegotiatedDetails()->GetCodec(
-        0,
-        &offerRecvCodec));
-  const JsepCodecDescription* offerSendCodec;
-  ASSERT_EQ(NS_OK,
-      offerPairs[1].mSending->GetNegotiatedDetails()->GetCodec(
-        0,
-        &offerSendCodec));
 
   auto answerPairs = mSessionAns.GetNegotiatedTrackPairs();
   ASSERT_EQ(2U, answerPairs.size());
   ASSERT_TRUE(answerPairs[1].mSending);
   ASSERT_TRUE(answerPairs[1].mReceiving);
   ASSERT_TRUE(answerPairs[1].mSending->GetNegotiatedDetails());
   ASSERT_TRUE(answerPairs[1].mReceiving->GetNegotiatedDetails());
   ASSERT_EQ(1U,
       answerPairs[1].mSending->GetNegotiatedDetails()->GetCodecCount());
   ASSERT_EQ(1U,
       answerPairs[1].mReceiving->GetNegotiatedDetails()->GetCodecCount());
-  const JsepCodecDescription* answerRecvCodec;
-  ASSERT_EQ(NS_OK,
-      answerPairs[1].mReceiving->GetNegotiatedDetails()->GetCodec(
-        0,
-        &answerRecvCodec));
-  const JsepCodecDescription* answerSendCodec;
-  ASSERT_EQ(NS_OK,
-      answerPairs[1].mSending->GetNegotiatedDetails()->GetCodec(
-        0,
-        &answerSendCodec));
 
 #if 0
   // H264 packetization mode 1
   ASSERT_EQ("126", fmtps[1].format);
   ASSERT_TRUE(fmtps[1].parameters);
   ASSERT_EQ(SdpRtpmapAttributeList::kH264, fmtps[1].parameters->codec_type);
 
   auto& parsed_h264_1_params =
@@ -2883,39 +2846,32 @@ static void ReplaceAll(const std::string
                        const std::string& with,
                        std::string* in)
 {
   while (in->find(toReplace) != std::string::npos) {
     Replace(toReplace, with, in);
   }
 }
 
-typedef enum
-{
-  kSending,
-  kReceiving
-} Direction;
-
 static void
 GetCodec(JsepSession& session,
          size_t pairIndex,
-         Direction direction,
+         sdp::Direction direction,
          size_t codecIndex,
          const JsepCodecDescription** codecOut)
 {
   *codecOut = nullptr;
   ASSERT_LT(pairIndex, session.GetNegotiatedTrackPairs().size());
   JsepTrackPair pair(session.GetNegotiatedTrackPairs().front());
   RefPtr<JsepTrack> track(
-      (direction == kSending) ? pair.mSending : pair.mReceiving);
+      (direction == sdp::kSend) ? pair.mSending : pair.mReceiving);
   ASSERT_TRUE(track);
   ASSERT_TRUE(track->GetNegotiatedDetails());
   ASSERT_LT(codecIndex, track->GetNegotiatedDetails()->GetCodecCount());
-  ASSERT_EQ(NS_OK,
-            track->GetNegotiatedDetails()->GetCodec(codecIndex, codecOut));
+  *codecOut = track->GetNegotiatedDetails()->GetCodec(codecIndex);
 }
 
 static void
 ForceH264(JsepSession& session, uint32_t profileLevelId)
 {
   for (JsepCodecDescription* codec : session.Codecs()) {
     if (codec->mName == "H264") {
       JsepVideoCodecDescription* h264 =
@@ -2940,40 +2896,40 @@ TEST_F(JsepSessionTest, TestH264Negotiat
 
   SetRemoteOffer(offer, CHECK_SUCCESS);
   std::string answer(CreateAnswer());
 
   SetRemoteAnswer(answer, CHECK_SUCCESS);
   SetLocalAnswer(answer, CHECK_SUCCESS);
 
   const JsepCodecDescription* offererSendCodec;
-  GetCodec(mSessionOff, 0, kSending, 0, &offererSendCodec);
+  GetCodec(mSessionOff, 0, sdp::kSend, 0, &offererSendCodec);
   ASSERT_TRUE(offererSendCodec);
   ASSERT_EQ("H264", offererSendCodec->mName);
   const JsepVideoCodecDescription* offererVideoSendCodec(
       static_cast<const JsepVideoCodecDescription*>(offererSendCodec));
   ASSERT_EQ((uint32_t)0x42e00d, offererVideoSendCodec->mProfileLevelId);
 
   const JsepCodecDescription* offererRecvCodec;
-  GetCodec(mSessionOff, 0, kReceiving, 0, &offererRecvCodec);
+  GetCodec(mSessionOff, 0, sdp::kRecv, 0, &offererRecvCodec);
   ASSERT_EQ("H264", offererRecvCodec->mName);
   const JsepVideoCodecDescription* offererVideoRecvCodec(
       static_cast<const JsepVideoCodecDescription*>(offererRecvCodec));
   ASSERT_EQ((uint32_t)0x42e00b, offererVideoRecvCodec->mProfileLevelId);
 
   const JsepCodecDescription* answererSendCodec;
-  GetCodec(mSessionAns, 0, kSending, 0, &answererSendCodec);
+  GetCodec(mSessionAns, 0, sdp::kSend, 0, &answererSendCodec);
   ASSERT_TRUE(answererSendCodec);
   ASSERT_EQ("H264", answererSendCodec->mName);
   const JsepVideoCodecDescription* answererVideoSendCodec(
       static_cast<const JsepVideoCodecDescription*>(answererSendCodec));
   ASSERT_EQ((uint32_t)0x42e00b, answererVideoSendCodec->mProfileLevelId);
 
   const JsepCodecDescription* answererRecvCodec;
-  GetCodec(mSessionAns, 0, kReceiving, 0, &answererRecvCodec);
+  GetCodec(mSessionAns, 0, sdp::kRecv, 0, &answererRecvCodec);
   ASSERT_EQ("H264", answererRecvCodec->mName);
   const JsepVideoCodecDescription* answererVideoRecvCodec(
       static_cast<const JsepVideoCodecDescription*>(answererRecvCodec));
   ASSERT_EQ((uint32_t)0x42e00d, answererVideoRecvCodec->mProfileLevelId);
 }
 
 TEST_F(JsepSessionTest, TestH264NegotiationFails)
 {
@@ -3013,17 +2969,17 @@ TEST_F(JsepSessionTest, TestH264Negotiat
 
   SetRemoteOffer(offer, CHECK_SUCCESS);
   std::string answer(CreateAnswer());
 
   SetRemoteAnswer(answer, CHECK_SUCCESS);
   SetLocalAnswer(answer, CHECK_SUCCESS);
 
   const JsepCodecDescription* answererSendCodec;
-  GetCodec(mSessionAns, 0, kSending, 0, &answererSendCodec);
+  GetCodec(mSessionAns, 0, sdp::kSend, 0, &answererSendCodec);
   ASSERT_TRUE(answererSendCodec);
   ASSERT_EQ("H264", answererSendCodec->mName);
   const JsepVideoCodecDescription* answererVideoSendCodec(
       static_cast<const JsepVideoCodecDescription*>(answererSendCodec));
   ASSERT_EQ((uint32_t)0x420010, answererVideoSendCodec->mProfileLevelId);
 }
 
 TEST_F(JsepSessionTest, TestH264NegotiationOffererNoFmtp)
@@ -3041,25 +2997,25 @@ TEST_F(JsepSessionTest, TestH264Negotiat
 
   SetRemoteOffer(offer, CHECK_SUCCESS);
   std::string answer(CreateAnswer());
 
   SetRemoteAnswer(answer, CHECK_SUCCESS);
   SetLocalAnswer(answer, CHECK_SUCCESS);
 
   const JsepCodecDescription* answererSendCodec;
-  GetCodec(mSessionAns, 0, kSending, 0, &answererSendCodec);
+  GetCodec(mSessionAns, 0, sdp::kSend, 0, &answererSendCodec);
   ASSERT_TRUE(answererSendCodec);
   ASSERT_EQ("H264", answererSendCodec->mName);
   const JsepVideoCodecDescription* answererVideoSendCodec(
       static_cast<const JsepVideoCodecDescription*>(answererSendCodec));
   ASSERT_EQ((uint32_t)0x420010, answererVideoSendCodec->mProfileLevelId);
 
   const JsepCodecDescription* answererRecvCodec;
-  GetCodec(mSessionAns, 0, kReceiving, 0, &answererRecvCodec);
+  GetCodec(mSessionAns, 0, sdp::kRecv, 0, &answererRecvCodec);
   ASSERT_EQ("H264", answererRecvCodec->mName);
   const JsepVideoCodecDescription* answererVideoRecvCodec(
       static_cast<const JsepVideoCodecDescription*>(answererRecvCodec));
   ASSERT_EQ((uint32_t)0x420010, answererVideoRecvCodec->mProfileLevelId);
 }
 
 TEST_F(JsepSessionTest, TestH264LevelAsymmetryDisallowedByOffererWithLowLevel)
 {
@@ -3081,25 +3037,25 @@ TEST_F(JsepSessionTest, TestH264LevelAsy
 
   SetRemoteAnswer(answer, CHECK_SUCCESS);
   SetLocalAnswer(answer, CHECK_SUCCESS);
 
   // Offerer doesn't know about the shenanigans we've pulled here, so will
   // behave normally, and we test the normal behavior elsewhere.
 
   const JsepCodecDescription* answererSendCodec;
-  GetCodec(mSessionAns, 0, kSending, 0, &answererSendCodec);
+  GetCodec(mSessionAns, 0, sdp::kSend, 0, &answererSendCodec);
   ASSERT_TRUE(answererSendCodec);
   ASSERT_EQ("H264", answererSendCodec->mName);
   const JsepVideoCodecDescription* answererVideoSendCodec(
       static_cast<const JsepVideoCodecDescription*>(answererSendCodec));
   ASSERT_EQ((uint32_t)0x42e00b, answererVideoSendCodec->mProfileLevelId);
 
   const JsepCodecDescription* answererRecvCodec;
-  GetCodec(mSessionAns, 0, kReceiving, 0, &answererRecvCodec);
+  GetCodec(mSessionAns, 0, sdp::kRecv, 0, &answererRecvCodec);
   ASSERT_EQ("H264", answererRecvCodec->mName);
   const JsepVideoCodecDescription* answererVideoRecvCodec(
       static_cast<const JsepVideoCodecDescription*>(answererRecvCodec));
   ASSERT_EQ((uint32_t)0x42e00b, answererVideoRecvCodec->mProfileLevelId);
 }
 
 TEST_F(JsepSessionTest, TestH264LevelAsymmetryDisallowedByOffererWithHighLevel)
 {
@@ -3121,25 +3077,25 @@ TEST_F(JsepSessionTest, TestH264LevelAsy
 
   SetRemoteAnswer(answer, CHECK_SUCCESS);
   SetLocalAnswer(answer, CHECK_SUCCESS);
 
   // Offerer doesn't know about the shenanigans we've pulled here, so will
   // behave normally, and we test the normal behavior elsewhere.
 
   const JsepCodecDescription* answererSendCodec;
-  GetCodec(mSessionAns, 0, kSending, 0, &answererSendCodec);
+  GetCodec(mSessionAns, 0, sdp::kSend, 0, &answererSendCodec);
   ASSERT_TRUE(answererSendCodec);
   ASSERT_EQ("H264", answererSendCodec->mName);
   const JsepVideoCodecDescription* answererVideoSendCodec(
       static_cast<const JsepVideoCodecDescription*>(answererSendCodec));
   ASSERT_EQ((uint32_t)0x42e00b, answererVideoSendCodec->mProfileLevelId);
 
   const JsepCodecDescription* answererRecvCodec;
-  GetCodec(mSessionAns, 0, kReceiving, 0, &answererRecvCodec);
+  GetCodec(mSessionAns, 0, sdp::kRecv, 0, &answererRecvCodec);
   ASSERT_EQ("H264", answererRecvCodec->mName);
   const JsepVideoCodecDescription* answererVideoRecvCodec(
       static_cast<const JsepVideoCodecDescription*>(answererRecvCodec));
   ASSERT_EQ((uint32_t)0x42e00b, answererVideoRecvCodec->mProfileLevelId);
 }
 
 TEST_F(JsepSessionTest, TestH264LevelAsymmetryDisallowedByAnswererWithLowLevel)
 {
@@ -3157,25 +3113,25 @@ TEST_F(JsepSessionTest, TestH264LevelAsy
   Replace("level-asymmetry-allowed=1",
           "level-asymmetry-allowed=0",
           &answer);
 
   SetRemoteAnswer(answer, CHECK_SUCCESS);
   SetLocalAnswer(answer, CHECK_SUCCESS);
 
   const JsepCodecDescription* offererSendCodec;
-  GetCodec(mSessionOff, 0, kSending, 0, &offererSendCodec);
+  GetCodec(mSessionOff, 0, sdp::kSend, 0, &offererSendCodec);
   ASSERT_TRUE(offererSendCodec);
   ASSERT_EQ("H264", offererSendCodec->mName);
   const JsepVideoCodecDescription* offererVideoSendCodec(
       static_cast<const JsepVideoCodecDescription*>(offererSendCodec));
   ASSERT_EQ((uint32_t)0x42e00b, offererVideoSendCodec->mProfileLevelId);
 
   const JsepCodecDescription* offererRecvCodec;
-  GetCodec(mSessionOff, 0, kReceiving, 0, &offererRecvCodec);
+  GetCodec(mSessionOff, 0, sdp::kRecv, 0, &offererRecvCodec);
   ASSERT_EQ("H264", offererRecvCodec->mName);
   const JsepVideoCodecDescription* offererVideoRecvCodec(
       static_cast<const JsepVideoCodecDescription*>(offererRecvCodec));
   ASSERT_EQ((uint32_t)0x42e00b, offererVideoRecvCodec->mProfileLevelId);
 
   // Answerer doesn't know we've pulled these shenanigans, it should act as if
   // it did not set level-asymmetry-required, and we already check that
   // elsewhere
@@ -3197,39 +3153,40 @@ TEST_F(JsepSessionTest, TestH264LevelAsy
   Replace("level-asymmetry-allowed=1",
           "level-asymmetry-allowed=0",
           &answer);
 
   SetRemoteAnswer(answer, CHECK_SUCCESS);
   SetLocalAnswer(answer, CHECK_SUCCESS);
 
   const JsepCodecDescription* offererSendCodec;
-  GetCodec(mSessionOff, 0, kSending, 0, &offererSendCodec);
+  GetCodec(mSessionOff, 0, sdp::kSend, 0, &offererSendCodec);
   ASSERT_TRUE(offererSendCodec);
   ASSERT_EQ("H264", offererSendCodec->mName);
   const JsepVideoCodecDescription* offererVideoSendCodec(
       static_cast<const JsepVideoCodecDescription*>(offererSendCodec));
   ASSERT_EQ((uint32_t)0x42e00b, offererVideoSendCodec->mProfileLevelId);
 
   const JsepCodecDescription* offererRecvCodec;
-  GetCodec(mSessionOff, 0, kReceiving, 0, &offererRecvCodec);
+  GetCodec(mSessionOff, 0, sdp::kRecv, 0, &offererRecvCodec);
   ASSERT_EQ("H264", offererRecvCodec->mName);
   const JsepVideoCodecDescription* offererVideoRecvCodec(
       static_cast<const JsepVideoCodecDescription*>(offererRecvCodec));
   ASSERT_EQ((uint32_t)0x42e00b, offererVideoRecvCodec->mProfileLevelId);
 
   // Answerer doesn't know we've pulled these shenanigans, it should act as if
   // it did not set level-asymmetry-required, and we already check that
   // elsewhere
 }
 
 TEST_P(JsepSessionTest, TestRejectMline)
 {
-  AddTracks(mSessionOff);
-  AddTracks(mSessionAns);
+  // We need to do this before adding tracks
+  types = BuildTypes(GetParam());
+  std::sort(types.begin(), types.end());
 
   switch (types.front()) {
     case SdpMediaSection::kAudio:
       // Sabotage audio
       EnsureNegotiationFailure(types.front(), "opus");
       break;
     case SdpMediaSection::kVideo:
       // Sabotage video
@@ -3238,16 +3195,19 @@ TEST_P(JsepSessionTest, TestRejectMline)
     case SdpMediaSection::kApplication:
       // Sabotage datachannel
       EnsureNegotiationFailure(types.front(), "webrtc-datachannel");
       break;
     default:
       ASSERT_TRUE(false) << "Unknown media type";
   }
 
+  AddTracks(mSessionOff);
+  AddTracks(mSessionAns);
+
   std::string offer = CreateOffer();
   mSessionOff.SetLocalDescription(kJsepSdpOffer, offer);
   mSessionAns.SetRemoteDescription(kJsepSdpOffer, offer);
 
   std::string answer = CreateAnswer();
 
   UniquePtr<Sdp> outputSdp(Parse(answer));
   ASSERT_TRUE(!!outputSdp);
@@ -3394,18 +3354,17 @@ TEST_F(JsepSessionTest, TestRtcpFbStar)
   SetLocalAnswer(answer, CHECK_SUCCESS);
   SetRemoteAnswer(answer, CHECK_SUCCESS);
 
   ASSERT_EQ(1U, mSessionAns.GetRemoteTracks().size());
   RefPtr<JsepTrack> track = mSessionAns.GetRemoteTracks()[0];
   ASSERT_TRUE(track->GetNegotiatedDetails());
   auto* details = track->GetNegotiatedDetails();
   for (size_t i = 0; i < details->GetCodecCount(); ++i) {
-    const JsepCodecDescription* codec;
-    ASSERT_EQ(NS_OK, details->GetCodec(i, &codec));
+    const JsepCodecDescription* codec = details->GetCodec(i);
     const JsepVideoCodecDescription* videoCodec =
       static_cast<const JsepVideoCodecDescription*>(codec);
     ASSERT_EQ(1U, videoCodec->mNackFbTypes.size());
     ASSERT_EQ("", videoCodec->mNackFbTypes[0]);
   }
 }
 
 TEST_F(JsepSessionTest, TestUniquePayloadTypes)
@@ -3530,38 +3489,38 @@ TEST_F(JsepSessionTest, StronglyPreferre
 
   types.push_back(SdpMediaSection::kVideo);
   AddTracks(mSessionOff, "video");
   AddTracks(mSessionAns, "video");
 
   OfferAnswer();
 
   const JsepCodecDescription* codec;
-  GetCodec(mSessionAns, 0, kSending, 0, &codec);
+  GetCodec(mSessionAns, 0, sdp::kSend, 0, &codec);
   ASSERT_TRUE(codec);
   ASSERT_EQ("H264", codec->mName);
-  GetCodec(mSessionAns, 0, kReceiving, 0, &codec);
+  GetCodec(mSessionAns, 0, sdp::kRecv, 0, &codec);
   ASSERT_TRUE(codec);
   ASSERT_EQ("H264", codec->mName);
 }
 
 TEST_F(JsepSessionTest, LowDynamicPayloadType)
 {
   SetPayloadTypeNumber(mSessionOff, "opus", "12");
   types.push_back(SdpMediaSection::kAudio);
   AddTracks(mSessionOff, "audio");
   AddTracks(mSessionAns, "audio");
 
   OfferAnswer();
   const JsepCodecDescription* codec;
-  GetCodec(mSessionAns, 0, kSending, 0, &codec);
+  GetCodec(mSessionAns, 0, sdp::kSend, 0, &codec);
   ASSERT_TRUE(codec);
   ASSERT_EQ("opus", codec->mName);
   ASSERT_EQ("12", codec->mDefaultPt);
-  GetCodec(mSessionAns, 0, kReceiving, 0, &codec);
+  GetCodec(mSessionAns, 0, sdp::kRecv, 0, &codec);
   ASSERT_TRUE(codec);
   ASSERT_EQ("opus", codec->mName);
   ASSERT_EQ("12", codec->mDefaultPt);
 }
 
 TEST_F(JsepSessionTest, PayloadTypeClash)
 {
   // Disable this so mSessionOff doesn't have a duplicate
@@ -3569,21 +3528,21 @@ TEST_F(JsepSessionTest, PayloadTypeClash
   SetPayloadTypeNumber(mSessionOff, "opus", "0");
   SetPayloadTypeNumber(mSessionAns, "PCMU", "0");
   types.push_back(SdpMediaSection::kAudio);
   AddTracks(mSessionOff, "audio");
   AddTracks(mSessionAns, "audio");
 
   OfferAnswer();
   const JsepCodecDescription* codec;
-  GetCodec(mSessionAns, 0, kSending, 0, &codec);
+  GetCodec(mSessionAns, 0, sdp::kSend, 0, &codec);
   ASSERT_TRUE(codec);
   ASSERT_EQ("opus", codec->mName);
   ASSERT_EQ("0", codec->mDefaultPt);
-  GetCodec(mSessionAns, 0, kReceiving, 0, &codec);
+  GetCodec(mSessionAns, 0, sdp::kRecv, 0, &codec);
   ASSERT_TRUE(codec);
   ASSERT_EQ("opus", codec->mName);
   ASSERT_EQ("0", codec->mDefaultPt);
 
   // Now, make sure that mSessionAns does not put a=rtpmap:0 PCMU in a reoffer,
   // since pt 0 is taken for opus (the answerer still supports PCMU, and will
   // reoffer it, but it should choose a new payload type for it)
   JsepOfferOptions options;