Bug 1141779: Fix H264 negotiation when level-asymmetry-allowed is not specified, and some cleanup. r=jesup
authorByron Campen [:bwc] <docfaraday@gmail.com>
Tue, 10 Mar 2015 17:35:50 -0700
changeset 263914 d9ba4c1d570ad71423da5e857dc88f3400d3df9b
parent 263913 efdffcda26710985e2349dfa1f6431c773aca061
child 263915 6deef776437b830a1a78561f7ca01a765a957dd5
push id4718
push userraliiev@mozilla.com
push dateMon, 11 May 2015 18:39:53 +0000
treeherdermozilla-beta@c20c4ef55f08 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjesup
bugs1141779
milestone39.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 1141779: Fix H264 negotiation when level-asymmetry-allowed is not specified, and some cleanup. r=jesup
media/webrtc/signaling/src/jsep/JsepCodecDescription.h
media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
media/webrtc/signaling/src/sdp/SdpAttribute.h
media/webrtc/signaling/test/jsep_session_unittest.cpp
--- a/media/webrtc/signaling/src/jsep/JsepCodecDescription.h
+++ b/media/webrtc/signaling/src/jsep/JsepCodecDescription.h
@@ -33,20 +33,19 @@ struct JsepCodecDescription {
         mEnabled(enabled)
   {
   }
   virtual ~JsepCodecDescription() {}
 
   virtual JsepCodecDescription* Clone() const = 0;
   virtual void AddFmtps(SdpFmtpAttributeList& fmtp) const = 0;
   virtual void AddRtcpFbs(SdpRtcpFbAttributeList& rtcpfb) const = 0;
-  virtual bool LoadFmtps(const SdpFmtpAttributeList::Parameters& params) = 0;
-  virtual bool LoadRtcpFbs(
-      const SdpRtcpFbAttributeList::Feedback& feedback) = 0;
 
+  // TODO(bug 1142105): This probably should be a helper function in
+  // /sdp
   static bool
   GetPtAsInt(const std::string& ptString, uint16_t* ptOutparam)
   {
     char* end;
     unsigned long pt = strtoul(ptString.c_str(), &end, 10);
     size_t length = static_cast<size_t>(end - ptString.c_str());
     if ((pt > UINT16_MAX) || (length != ptString.size())) {
       return false;
@@ -75,27 +74,50 @@ struct JsepCodecDescription {
     }
 
     const SdpRtpmapAttributeList::Rtpmap& entry = rtpmap.GetEntry(fmt);
 
     if (mType == remoteMsection.GetMediaType()
         && (mName == entry.name)
         && (mClock == entry.clock)
         && (mChannels == entry.channels)) {
-      return ParametersMatch(FindParameters(entry.pt, remoteMsection));
+      return ParametersMatch(entry.pt, remoteMsection);
     }
     return false;
   }
 
   virtual bool
-  ParametersMatch(const SdpFmtpAttributeList::Parameters* fmtp) const
+  ParametersMatch(const std::string& fmt,
+                  const SdpMediaSection& remoteMsection) const
   {
     return true;
   }
 
+  UniquePtr<JsepCodecDescription>
+  MakeNegotiatedCodec(const std::string& pt,
+                      const SdpMediaSection& remoteMsection) const
+  {
+    UniquePtr<JsepCodecDescription> negotiated(Clone());
+
+    if (!negotiated->Negotiate(pt, remoteMsection)) {
+      negotiated.reset();
+    }
+
+    return negotiated;
+  }
+
+  virtual bool
+  Negotiate(const std::string& pt, const SdpMediaSection& remoteMsection)
+  {
+    mDefaultPt = pt;
+    return true;
+  }
+
+  // TODO(bug 1142105): This probably should be a helper function in
+  // /sdp
   static const SdpFmtpAttributeList::Parameters*
   FindParameters(const std::string& pt,
                  const mozilla::SdpMediaSection& remoteMsection)
   {
     const SdpAttributeList& attrs = remoteMsection.GetAttributeList();
 
     if (attrs.HasAttribute(SdpAttribute::kFmtpAttribute)) {
       const SdpFmtpAttributeList& fmtps = attrs.GetFmtp();
@@ -103,50 +125,56 @@ struct JsepCodecDescription {
         if (i->format == pt && i->parameters) {
           return i->parameters.get();
         }
       }
     }
     return nullptr;
   }
 
-  virtual JsepCodecDescription*
-  MakeNegotiatedCodec(const mozilla::SdpMediaSection& remoteMsection,
-                      const std::string& pt,
-                      bool sending) const
+  UniquePtr<JsepCodecDescription>
+  MakeSendCodec(const mozilla::SdpMediaSection& remoteMsection,
+                const std::string& pt) const
   {
-    UniquePtr<JsepCodecDescription> negotiated(Clone());
-    negotiated->mDefaultPt = pt;
-
-    const SdpAttributeList& attrs = remoteMsection.GetAttributeList();
+    UniquePtr<JsepCodecDescription> sendCodec(Clone());
+    sendCodec->mDefaultPt = pt;
 
-    if (sending) {
-      auto* parameters = FindParameters(negotiated->mDefaultPt, remoteMsection);
-      if (parameters) {
-        if (!negotiated->LoadFmtps(*parameters)) {
-          // Remote parameters were invalid
-          return nullptr;
-        }
-      }
-    } else {
-      // If a receive track, we need to pay attention to remote end's rtcp-fb
-      if (attrs.HasAttribute(SdpAttribute::kRtcpFbAttribute)) {
-        auto& rtcpfbs = attrs.GetRtcpFb().mFeedbacks;
-        for (auto i = rtcpfbs.begin(); i != rtcpfbs.end(); ++i) {
-          if (i->pt == negotiated->mDefaultPt || i->pt == "*") {
-            if (!negotiated->LoadRtcpFbs(*i)) {
-              // Remote parameters were invalid
-              return nullptr;
-            }
-          }
-        }
-      }
+    if (!sendCodec->LoadSendParameters(remoteMsection, pt)) {
+      sendCodec.reset();
     }
 
-    return negotiated.release();
+    return sendCodec;
+  }
+
+  UniquePtr<JsepCodecDescription>
+  MakeRecvCodec(const mozilla::SdpMediaSection& remoteMsection,
+                const std::string& pt) const
+  {
+    UniquePtr<JsepCodecDescription> recvCodec(Clone());
+    recvCodec->mDefaultPt = pt;
+
+    if (!recvCodec->LoadRecvParameters(remoteMsection, pt)) {
+      recvCodec.reset();
+    }
+
+    return recvCodec;
+  }
+
+  virtual bool LoadSendParameters(
+      const mozilla::SdpMediaSection& remoteMsection,
+      const std::string& pt)
+  {
+    return true;
+  }
+
+  virtual bool LoadRecvParameters(
+      const mozilla::SdpMediaSection& remoteMsection,
+      const std::string& 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
@@ -229,30 +257,16 @@ struct JsepAudioCodecDescription : publi
   }
 
   virtual void
   AddRtcpFbs(SdpRtcpFbAttributeList& rtcpfb) const override
   {
     // TODO: Do we want to add anything?
   }
 
-  virtual bool
-  LoadFmtps(const SdpFmtpAttributeList::Parameters& params) override
-  {
-    // TODO
-    return true;
-  }
-
-  virtual bool
-  LoadRtcpFbs(const SdpRtcpFbAttributeList::Feedback& feedback) override
-  {
-    // Nothing to do
-    return true;
-  }
-
   JSEP_CODEC_CLONE(JsepAudioCodecDescription)
 
   uint32_t mPacketSize;
   uint32_t mBitrate;
 };
 
 struct JsepVideoCodecDescription : public JsepCodecDescription {
   JsepVideoCodecDescription(const std::string& defaultPt,
@@ -311,57 +325,195 @@ struct JsepVideoCodecDescription : publi
     // Just hard code for now
     rtcpfb.PushEntry(mDefaultPt, SdpRtcpFbAttributeList::kNack);
     rtcpfb.PushEntry(
         mDefaultPt, SdpRtcpFbAttributeList::kNack, SdpRtcpFbAttributeList::pli);
     rtcpfb.PushEntry(
         mDefaultPt, SdpRtcpFbAttributeList::kCcm, SdpRtcpFbAttributeList::fir);
   }
 
+  SdpFmtpAttributeList::H264Parameters
+  GetH264Parameters(const std::string& pt,
+                    const SdpMediaSection& msection) const
+  {
+    // Will contain defaults if nothing else
+    SdpFmtpAttributeList::H264Parameters result;
+    auto* params = FindParameters(pt, msection);
+
+    if (params && params->codec_type == SdpRtpmapAttributeList::kH264) {
+      result =
+        static_cast<const SdpFmtpAttributeList::H264Parameters&>(*params);
+    }
+
+    return result;
+  }
+
+  SdpFmtpAttributeList::VP8Parameters
+  GetVP8Parameters(const std::string& pt,
+                   const SdpMediaSection& msection) const
+  {
+    SdpRtpmapAttributeList::CodecType expectedType(
+        mName == "VP8" ?
+        SdpRtpmapAttributeList::kVP8 :
+        SdpRtpmapAttributeList::kVP9);
+
+    // Will contain defaults if nothing else
+    SdpFmtpAttributeList::VP8Parameters result(expectedType);
+    auto* params = FindParameters(pt, msection);
+
+    if (params && params->codec_type == expectedType) {
+      result =
+        static_cast<const SdpFmtpAttributeList::VP8Parameters&>(*params);
+    }
+
+    return result;
+  }
+
   virtual bool
-  LoadFmtps(const SdpFmtpAttributeList::Parameters& params) override
+  Negotiate(const std::string& pt,
+            const SdpMediaSection& remoteMsection) override
+  {
+    if (mName == "H264") {
+      SdpFmtpAttributeList::H264Parameters h264Params(
+          GetH264Parameters(pt, remoteMsection));
+      if (!h264Params.level_asymmetry_allowed) {
+        SetSaneH264Level(std::min(GetSaneH264Level(h264Params.profile_level_id),
+                                  GetSaneH264Level(mProfileLevelId)),
+                         &mProfileLevelId);
+      }
+
+      // TODO(bug 1143709): max-recv-level support
+    }
+
+    return JsepCodecDescription::Negotiate(pt, remoteMsection);
+  }
+
+  // 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)
   {
-    switch (params.codec_type) {
-      case SdpRtpmapAttributeList::kH264:
-        LoadH264Parameters(params);
-        break;
-      case SdpRtpmapAttributeList::kVP9:
-        // VP8 and VP9 share the same SDP parameters thus far
-      case SdpRtpmapAttributeList::kVP8:
-        LoadVP8Parameters(params);
-        break;
-      case SdpRtpmapAttributeList::kiLBC:
-      case SdpRtpmapAttributeList::kiSAC:
-      case SdpRtpmapAttributeList::kOpus:
-      case SdpRtpmapAttributeList::kG722:
-      case SdpRtpmapAttributeList::kPCMU:
-      case SdpRtpmapAttributeList::kPCMA:
-      case SdpRtpmapAttributeList::kOtherCodec:
-        MOZ_ASSERT(false, "Invalid codec type for video");
+    uint32_t profileIdc = (profileLevelId >> 16);
+
+    if (profileIdc == 0x42 || profileIdc == 0x4D || profileIdc == 0x58) {
+      if ((profileLevelId & 0x10FF) == 0x100B) {
+        // Level 1b
+        return 0xAB;
+      }
+    }
+
+    uint32_t level = profileLevelId & 0xFF;
+
+    if (level == 0x09) {
+      // Another way to encode level 1b
+      return 0xAB;
+    }
+
+    return level << 4;
+  }
+
+  static void
+  SetSaneH264Level(uint32_t level, uint32_t* profileLevelId)
+  {
+    uint32_t profileIdc = (*profileLevelId >> 16);
+    uint32_t levelMask = 0xFF;
+
+    if (profileIdc == 0x42 || profileIdc == 0x4d || profileIdc == 0x58) {
+      levelMask = 0x10FF;
+      if (level == 0xAB) {
+        // Level 1b
+        level = 0x100B;
+      } else {
+        // Not 1b, just shift
+        level = level >> 4;
+      }
+    } else if (level == 0xAB) {
+      // Another way to encode 1b
+      level = 0x09;
+    } else {
+      // Not 1b, just shift
+      level = level >> 4;
+    }
+
+    *profileLevelId = (*profileLevelId & ~levelMask) | level;
+  }
+
+  virtual bool
+  LoadSendParameters(const mozilla::SdpMediaSection& remoteMsection,
+                     const std::string& pt) override
+  {
+
+    if (mName == "H264") {
+      SdpFmtpAttributeList::H264Parameters h264Params(
+          GetH264Parameters(pt, 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(pt, remoteMsection));
+
+      mMaxFs = vp8Params.max_fs;
+      mMaxFr = vp8Params.max_fr;
     }
     return true;
   }
 
   virtual bool
-  LoadRtcpFbs(const SdpRtcpFbAttributeList::Feedback& feedback) override
+  LoadRecvParameters(const mozilla::SdpMediaSection& remoteMsection,
+                     const std::string& pt) override
   {
-    switch (feedback.type) {
-      case SdpRtcpFbAttributeList::kAck:
-        mAckFbTypes.push_back(feedback.parameter);
-        break;
-      case SdpRtcpFbAttributeList::kCcm:
-        mCcmFbTypes.push_back(feedback.parameter);
-        break;
-      case SdpRtcpFbAttributeList::kNack:
-        mNackFbTypes.push_back(feedback.parameter);
-        break;
-      case SdpRtcpFbAttributeList::kApp:
-      case SdpRtcpFbAttributeList::kTrrInt:
-        // We don't support these, ignore.
-        {}
+    const SdpAttributeList& attrs(remoteMsection.GetAttributeList());
+
+    if (attrs.HasAttribute(SdpAttribute::kRtcpFbAttribute)) {
+      auto& rtcpfbs = attrs.GetRtcpFb().mFeedbacks;
+      for (auto i = rtcpfbs.begin(); i != rtcpfbs.end(); ++i) {
+        if (i->pt == mDefaultPt || i->pt == "*") {
+          switch (i->type) {
+            case SdpRtcpFbAttributeList::kAck:
+              mAckFbTypes.push_back(i->parameter);
+              break;
+            case SdpRtcpFbAttributeList::kCcm:
+              mCcmFbTypes.push_back(i->parameter);
+              break;
+            case SdpRtcpFbAttributeList::kNack:
+              mNackFbTypes.push_back(i->parameter);
+              break;
+            case SdpRtcpFbAttributeList::kApp:
+            case SdpRtcpFbAttributeList::kTrrInt:
+              // We don't support these, ignore.
+              {}
+          }
+        }
+      }
+    }
+
+    if (mName == "H264") {
+      SdpFmtpAttributeList::H264Parameters h264Params(
+          GetH264Parameters(pt, remoteMsection));
+      if (!h264Params.level_asymmetry_allowed) {
+        SetSaneH264Level(std::min(GetSaneH264Level(h264Params.profile_level_id),
+                                  GetSaneH264Level(mProfileLevelId)),
+                         &mProfileLevelId);
+      }
     }
     return true;
   }
 
   enum Subprofile {
     kH264ConstrainedBaseline,
     kH264Baseline,
     kH264Main,
@@ -480,70 +632,36 @@ struct JsepVideoCodecDescription : publi
     if ((profileLevelId & 0xFFFF00) == 0x2C1000) {
       return kH264CALVC44;
     }
 
     return kH264UnknownSubprofile;
   }
 
   virtual bool
-  ParametersMatch(const SdpFmtpAttributeList::Parameters* fmtp) const
-      override
+  ParametersMatch(const std::string& fmt,
+                  const SdpMediaSection& remoteMsection) const override
   {
     if (mName == "H264") {
-      if (!fmtp) {
-        // No fmtp means that we cannot assume level asymmetry is allowed,
-        // and since we have no way of knowing the profile-level-id, we can't
-        // say that we match.
+      SdpFmtpAttributeList::H264Parameters h264Params(
+          GetH264Parameters(fmt, remoteMsection));
+
+      if (h264Params.packetization_mode != mPacketizationMode) {
         return false;
       }
 
-      auto* h264Params =
-          static_cast<const SdpFmtpAttributeList::H264Parameters*>(fmtp);
-
-      if (!h264Params->level_asymmetry_allowed) {
-        if (GetSubprofile(h264Params->profile_level_id) !=
-            GetSubprofile(mProfileLevelId)) {
-          return false;
-        }
-      }
-
-      if (h264Params->packetization_mode != mPacketizationMode) {
+      if (GetSubprofile(h264Params.profile_level_id) !=
+          GetSubprofile(mProfileLevelId)) {
         return false;
       }
     }
+
     return true;
   }
 
-  void
-  LoadH264Parameters(const SdpFmtpAttributeList::Parameters& params)
-  {
-    const SdpFmtpAttributeList::H264Parameters& h264Params =
-        static_cast<const SdpFmtpAttributeList::H264Parameters&>(params);
-
-    mMaxFs = h264Params.max_fs;
-    mProfileLevelId = h264Params.profile_level_id;
-    mPacketizationMode = h264Params.packetization_mode;
-    mMaxMbps = h264Params.max_mbps;
-    mMaxCpb = h264Params.max_cpb;
-    mMaxDpb = h264Params.max_dpb;
-    mMaxBr = h264Params.max_br;
-    mSpropParameterSets = h264Params.sprop_parameter_sets;
-  }
-
-  void
-  LoadVP8Parameters(const SdpFmtpAttributeList::Parameters& params)
-  {
-    const SdpFmtpAttributeList::VP8Parameters& vp8Params =
-        static_cast<const SdpFmtpAttributeList::VP8Parameters&>(params);
-
-    mMaxFs = vp8Params.max_fs;
-    mMaxFr = vp8Params.max_fr;
-  }
-
   JSEP_CODEC_CLONE(JsepVideoCodecDescription)
 
   std::vector<std::string> mAckFbTypes;
   std::vector<std::string> mNackFbTypes;
   std::vector<std::string> mCcmFbTypes;
 
   uint32_t mMaxFs;
 
@@ -575,30 +693,16 @@ struct JsepApplicationCodecDescription :
   }
 
   virtual void
   AddRtcpFbs(SdpRtcpFbAttributeList& rtcpfb) const override
   {
     // Nothing to do here.
   }
 
-  virtual bool
-  LoadFmtps(const SdpFmtpAttributeList::Parameters& params) override
-  {
-    // TODO: Is there anything to do here?
-    return true;
-  }
-
-  virtual bool
-  LoadRtcpFbs(const SdpRtcpFbAttributeList::Feedback& feedback) override
-  {
-    // Nothing to do
-    return true;
-  }
-
   JSEP_CODEC_CLONE(JsepApplicationCodecDescription)
 
   // Override, uses sctpmap instead of rtpmap
   virtual bool
   Matches(const std::string& fmt,
           const SdpMediaSection& remoteMsection) const override
   {
     auto& attrs = remoteMsection.GetAttributeList();
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
@@ -911,18 +911,22 @@ void
 JsepSessionImpl::AddCommonCodecs(const SdpMediaSection& remoteMsection,
                                  SdpMediaSection* msection)
 {
   const std::vector<std::string>& formats = remoteMsection.GetFormats();
 
   for (auto fmt = formats.begin(); fmt != formats.end(); ++fmt) {
     JsepCodecDescription* codec = FindMatchingCodec(*fmt, remoteMsection);
     if (codec) {
-      codec->mDefaultPt = *fmt; // Reflect the other side's PT
-      codec->AddToMediaSection(*msection);
+      UniquePtr<JsepCodecDescription> negotiated(
+          codec->MakeNegotiatedCodec(*fmt, remoteMsection));
+      if (negotiated) {
+        negotiated->AddToMediaSection(*msection);
+        codec->mDefaultPt = *fmt; // Remember the other side's PT
+      }
       // TODO(bug 1099351): Once bug 1073475 is fixed on all supported
       // versions, we can remove this limitation.
       break;
     }
   }
 }
 
 void
@@ -1676,40 +1680,51 @@ JsepSessionImpl::NegotiateTrack(const Sd
     JsepCodecDescription* codec = FindMatchingCodec(*fmt, remoteMsection);
 
     if (!codec) {
       continue;
     }
 
     bool sending = (direction == JsepTrack::kJsepTrackSending);
 
-    // We need to take the remote side's parameters into account so we can
-    // configure our send media.
-    // |codec| is assumed to have the necessary state about our own config
-    // in order to negotiate.
-    JsepCodecDescription* negotiated =
-        codec->MakeNegotiatedCodec(remoteMsection, *fmt, sending);
-
-    if (!negotiated) {
+    // 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 local codec config (eg; rtcp-fb), others
+    // will come from the remote SDP (eg; max-fps), and still others can only be
+    // determined by inspecting both local config and remote SDP (eg;
+    // profile-level-id when level-asymmetry-allowed is 0).
+    UniquePtr<JsepCodecDescription> sendOrReceiveCodec;
+
+    if (sending) {
+      sendOrReceiveCodec =
+        Move(codec->MakeSendCodec(remoteMsection, *fmt));
+    } else {
+      sendOrReceiveCodec =
+        Move(codec->MakeRecvCodec(remoteMsection, *fmt));
+    }
+
+    if (!sendOrReceiveCodec) {
       continue;
     }
 
     if (remoteMsection.GetMediaType() == SdpMediaSection::kAudio ||
         remoteMsection.GetMediaType() == SdpMediaSection::kVideo) {
       // Sanity-check that payload type can work with RTP
       uint16_t payloadType;
-      if (!negotiated->GetPtAsInt(&payloadType) ||
+      if (!sendOrReceiveCodec->GetPtAsInt(&payloadType) ||
           payloadType > UINT8_MAX) {
         JSEP_SET_ERROR("audio/video payload type is not an 8 bit unsigned int: "
                        << *fmt);
         return NS_ERROR_INVALID_ARG;
       }
     }
 
-    negotiatedDetails->mCodecs.push_back(negotiated);
+    negotiatedDetails->mCodecs.push_back(sendOrReceiveCodec.release());
     break;
   }
 
   if (negotiatedDetails->mCodecs.empty()) {
     JSEP_SET_ERROR("Failed to negotiate codec details for all codecs");
     return NS_ERROR_INVALID_ARG;
   }
 
--- a/media/webrtc/signaling/src/sdp/SdpAttribute.h
+++ b/media/webrtc/signaling/src/sdp/SdpAttribute.h
@@ -938,21 +938,23 @@ public:
     virtual void Serialize(std::ostream& os) const = 0;
 
     SdpRtpmapAttributeList::CodecType codec_type;
   };
 
   class H264Parameters : public Parameters
   {
   public:
+    static const uint32_t kDefaultProfileLevelId = 0x420010;
+
     H264Parameters()
         : Parameters(SdpRtpmapAttributeList::kH264),
           packetization_mode(0),
           level_asymmetry_allowed(false),
-          profile_level_id(0),
+          profile_level_id(kDefaultProfileLevelId),
           max_mbps(0),
           max_fs(0),
           max_cpb(0),
           max_dpb(0),
           max_br(0)
     {
       memset(sprop_parameter_sets, 0, sizeof(sprop_parameter_sets));
     }
--- a/media/webrtc/signaling/test/jsep_session_unittest.cpp
+++ b/media/webrtc/signaling/test/jsep_session_unittest.cpp
@@ -2437,16 +2437,352 @@ TEST_F(JsepSessionTest, ValidateAnswered
         fmtps[2].parameters.get());
 
   ASSERT_EQ((uint32_t)0x42a00d, 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);
 #endif
 }
 
+static void
+Replace(const std::string& toReplace,
+        const std::string& with,
+        std::string* in)
+{
+  size_t pos = in->find(toReplace);
+  ASSERT_NE(std::string::npos, pos);
+  in->replace(pos, toReplace.size(), with);
+}
+
+static void
+GetCodec(JsepSession& session,
+         size_t pairIndex,
+         bool sending,
+         size_t codecIndex,
+         const JsepCodecDescription** codecOut)
+{
+  *codecOut = nullptr;
+  ASSERT_LT(pairIndex, session.GetNegotiatedTrackPairs().size());
+  JsepTrackPair pair(session.GetNegotiatedTrackPairs().front());
+  RefPtr<JsepTrack> track(sending ? 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));
+}
+
+static void
+ForceH264(JsepSession& session, uint32_t profileLevelId)
+{
+  for (JsepCodecDescription* codec : session.Codecs()) {
+    if (codec->mName == "H264") {
+      JsepVideoCodecDescription* h264 =
+          static_cast<JsepVideoCodecDescription*>(codec);
+      h264->mProfileLevelId = profileLevelId;
+    } else {
+      codec->mEnabled = false;
+    }
+  }
+}
+
+TEST_F(JsepSessionTest, TestH264Negotiation)
+{
+  ForceH264(mSessionOff, 0x42e00b);
+  ForceH264(mSessionAns, 0x42e00d);
+
+  AddTracks(mSessionOff, "video");
+  AddTracks(mSessionAns, "video");
+
+  std::string offer(CreateOffer());
+  SetLocalOffer(offer, CHECK_SUCCESS);
+
+  SetRemoteOffer(offer, CHECK_SUCCESS);
+  std::string answer(CreateAnswer());
+
+  SetRemoteAnswer(answer, CHECK_SUCCESS);
+  SetLocalAnswer(answer, CHECK_SUCCESS);
+
+  const JsepCodecDescription* offererSendCodec;
+  GetCodec(mSessionOff, 0, true, 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, false, 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, true, 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, false, 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)
+{
+  ForceH264(mSessionOff, 0x42000b);
+  ForceH264(mSessionAns, 0x42e00d);
+
+  AddTracks(mSessionOff, "video");
+  AddTracks(mSessionAns, "video");
+
+  std::string offer(CreateOffer());
+  SetLocalOffer(offer, CHECK_SUCCESS);
+
+  SetRemoteOffer(offer, CHECK_SUCCESS);
+  std::string answer(CreateAnswer());
+
+  SetRemoteAnswer(answer, CHECK_SUCCESS);
+  SetLocalAnswer(answer, CHECK_SUCCESS);
+
+  ASSERT_EQ(0U, mSessionOff.GetNegotiatedTrackPairs().size());
+  ASSERT_EQ(0U, mSessionAns.GetNegotiatedTrackPairs().size());
+}
+
+TEST_F(JsepSessionTest, TestH264NegotiationOffererDefault)
+{
+  ForceH264(mSessionOff, 0x42000d);
+  ForceH264(mSessionAns, 0x42000d);
+
+  AddTracks(mSessionOff, "video");
+  AddTracks(mSessionAns, "video");
+
+  std::string offer(CreateOffer());
+  SetLocalOffer(offer, CHECK_SUCCESS);
+
+  Replace("profile-level-id=42000d",
+          "some-unknown-param=0",
+          &offer);
+
+  SetRemoteOffer(offer, CHECK_SUCCESS);
+  std::string answer(CreateAnswer());
+
+  SetRemoteAnswer(answer, CHECK_SUCCESS);
+  SetLocalAnswer(answer, CHECK_SUCCESS);
+
+  const JsepCodecDescription* answererSendCodec;
+  GetCodec(mSessionAns, 0, true, 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)
+{
+  ForceH264(mSessionOff, 0x42000d);
+  ForceH264(mSessionAns, 0x42001e);
+
+  AddTracks(mSessionOff, "video");
+  AddTracks(mSessionAns, "video");
+
+  std::string offer(CreateOffer());
+  SetLocalOffer(offer, CHECK_SUCCESS);
+
+  Replace("a=fmtp", "a=oops", &offer);
+
+  SetRemoteOffer(offer, CHECK_SUCCESS);
+  std::string answer(CreateAnswer());
+
+  SetRemoteAnswer(answer, CHECK_SUCCESS);
+  SetLocalAnswer(answer, CHECK_SUCCESS);
+
+  const JsepCodecDescription* answererSendCodec;
+  GetCodec(mSessionAns, 0, true, 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, false, 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)
+{
+  ForceH264(mSessionOff, 0x42e00b);
+  ForceH264(mSessionAns, 0x42e00d);
+
+  AddTracks(mSessionOff, "video");
+  AddTracks(mSessionAns, "video");
+
+  std::string offer(CreateOffer());
+  SetLocalOffer(offer, CHECK_SUCCESS);
+
+  Replace("level-asymmetry-allowed=1",
+          "level-asymmetry-allowed=0",
+          &offer);
+
+  SetRemoteOffer(offer, CHECK_SUCCESS);
+  std::string answer(CreateAnswer());
+
+  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, true, 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, false, 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)
+{
+  ForceH264(mSessionOff, 0x42e00d);
+  ForceH264(mSessionAns, 0x42e00b);
+
+  AddTracks(mSessionOff, "video");
+  AddTracks(mSessionAns, "video");
+
+  std::string offer(CreateOffer());
+  SetLocalOffer(offer, CHECK_SUCCESS);
+
+  Replace("level-asymmetry-allowed=1",
+          "level-asymmetry-allowed=0",
+          &offer);
+
+  SetRemoteOffer(offer, CHECK_SUCCESS);
+  std::string answer(CreateAnswer());
+
+  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, true, 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, false, 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)
+{
+  ForceH264(mSessionOff, 0x42e00d);
+  ForceH264(mSessionAns, 0x42e00b);
+
+  AddTracks(mSessionOff, "video");
+  AddTracks(mSessionAns, "video");
+
+  std::string offer(CreateOffer());
+  SetLocalOffer(offer, CHECK_SUCCESS);
+  SetRemoteOffer(offer, CHECK_SUCCESS);
+  std::string answer(CreateAnswer());
+
+  Replace("level-asymmetry-allowed=1",
+          "level-asymmetry-allowed=0",
+          &answer);
+
+  SetRemoteAnswer(answer, CHECK_SUCCESS);
+  SetLocalAnswer(answer, CHECK_SUCCESS);
+
+  const JsepCodecDescription* offererSendCodec;
+  GetCodec(mSessionOff, 0, true, 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, false, 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_F(JsepSessionTest, TestH264LevelAsymmetryDisallowedByAnswererWithHighLevel)
+{
+  ForceH264(mSessionOff, 0x42e00b);
+  ForceH264(mSessionAns, 0x42e00d);
+
+  AddTracks(mSessionOff, "video");
+  AddTracks(mSessionAns, "video");
+
+  std::string offer(CreateOffer());
+  SetLocalOffer(offer, CHECK_SUCCESS);
+  SetRemoteOffer(offer, CHECK_SUCCESS);
+  std::string answer(CreateAnswer());
+
+  Replace("level-asymmetry-allowed=1",
+          "level-asymmetry-allowed=0",
+          &answer);
+
+  SetRemoteAnswer(answer, CHECK_SUCCESS);
+  SetLocalAnswer(answer, CHECK_SUCCESS);
+
+  const JsepCodecDescription* offererSendCodec;
+  GetCodec(mSessionOff, 0, true, 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, false, 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);
 
   switch (types.front()) {
     case SdpMediaSection::kAudio:
       // Sabotage audio
@@ -2685,16 +3021,58 @@ TEST_F(JsepSessionTest, TestUniquePayloa
 
   ASSERT_TRUE(answerPairs[2].mReceiving);
   ASSERT_TRUE(answerPairs[2].mReceiving->GetNegotiatedDetails());
   ASSERT_NE(0U,
       answerPairs[2].mReceiving->GetNegotiatedDetails()->
       GetUniquePayloadTypes().size());
 }
 
+TEST(H264ProfileLevelIdTest, TestLevelComparisons)
+{
+  ASSERT_LT(JsepVideoCodecDescription::GetSaneH264Level(0x421D0B), // 1b
+            JsepVideoCodecDescription::GetSaneH264Level(0x420D0B)); // 1.1
+  ASSERT_LT(JsepVideoCodecDescription::GetSaneH264Level(0x420D0A), // 1.0
+            JsepVideoCodecDescription::GetSaneH264Level(0x421D0B)); // 1b
+  ASSERT_LT(JsepVideoCodecDescription::GetSaneH264Level(0x420D0A), // 1.0
+            JsepVideoCodecDescription::GetSaneH264Level(0x420D0B)); // 1.1
+
+  ASSERT_LT(JsepVideoCodecDescription::GetSaneH264Level(0x640009), // 1b
+            JsepVideoCodecDescription::GetSaneH264Level(0x64000B)); // 1.1
+  ASSERT_LT(JsepVideoCodecDescription::GetSaneH264Level(0x64000A), // 1.0
+            JsepVideoCodecDescription::GetSaneH264Level(0x640009)); // 1b
+  ASSERT_LT(JsepVideoCodecDescription::GetSaneH264Level(0x64000A), // 1.0
+            JsepVideoCodecDescription::GetSaneH264Level(0x64000B)); // 1.1
+}
+
+TEST(H264ProfileLevelIdTest, TestLevelSetting)
+{
+  uint32_t profileLevelId = 0x420D0A;
+  JsepVideoCodecDescription::SetSaneH264Level(
+      JsepVideoCodecDescription::GetSaneH264Level(0x42100B),
+      &profileLevelId);
+  ASSERT_EQ((uint32_t)0x421D0B, profileLevelId);
+
+  JsepVideoCodecDescription::SetSaneH264Level(
+      JsepVideoCodecDescription::GetSaneH264Level(0x42000A),
+      &profileLevelId);
+  ASSERT_EQ((uint32_t)0x420D0A, profileLevelId);
+
+  profileLevelId = 0x6E100A;
+  JsepVideoCodecDescription::SetSaneH264Level(
+      JsepVideoCodecDescription::GetSaneH264Level(0x640009),
+      &profileLevelId);
+  ASSERT_EQ((uint32_t)0x6E1009, profileLevelId);
+
+  JsepVideoCodecDescription::SetSaneH264Level(
+      JsepVideoCodecDescription::GetSaneH264Level(0x64000B),
+      &profileLevelId);
+  ASSERT_EQ((uint32_t)0x6E100B, profileLevelId);
+}
+
 } // namespace mozilla
 
 int
 main(int argc, char** argv)
 {
   // Prevents some log spew
   ScopedXPCOM xpcom("jsep_session_unittest");