Bug 1291714 - sdp changes to support DTMF signaling. r=bwc draft
authorMichael Froman <mfroman@mozilla.com>
Thu, 15 Sep 2016 22:39:04 -0500
changeset 420251 e71f97670677c51b96a477c5cfb95f91a3fdef87
parent 420017 955840bfd3c20eb24dd5a01be27bdc55c489a285
child 420252 0e65711f7a36f75c8956efb0860fd63f7890a279
push id31147
push userbmo:mfroman@nostrum.com
push dateMon, 03 Oct 2016 21:00:03 +0000
reviewersbwc
bugs1291714
milestone52.0a1
Bug 1291714 - sdp changes to support DTMF signaling. r=bwc MozReview-Commit-ID: 7etTBsz3sM5
media/webrtc/signaling/src/jsep/JsepCodecDescription.h
media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
media/webrtc/signaling/src/jsep/JsepTrack.cpp
media/webrtc/signaling/src/media-conduit/CodecConfig.h
media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
media/webrtc/signaling/src/sdp/SdpAttribute.cpp
media/webrtc/signaling/src/sdp/SdpAttribute.h
media/webrtc/signaling/src/sdp/SdpMediaSection.cpp
media/webrtc/signaling/src/sdp/SdpMediaSection.h
media/webrtc/signaling/src/sdp/SipccSdpAttributeList.cpp
media/webrtc/signaling/src/sdp/sipcc/ccsdp.h
media/webrtc/signaling/src/sdp/sipcc/sdp.h
media/webrtc/signaling/src/sdp/sipcc/sdp_access.c
media/webrtc/signaling/src/sdp/sipcc/sdp_attr.c
media/webrtc/signaling/test/jsep_session_unittest.cpp
media/webrtc/signaling/test/jsep_track_unittest.cpp
media/webrtc/signaling/test/sdp_unittests.cpp
--- a/media/webrtc/signaling/src/jsep/JsepCodecDescription.h
+++ b/media/webrtc/signaling/src/jsep/JsepCodecDescription.h
@@ -129,17 +129,18 @@ class JsepAudioCodecDescription : public
                             uint32_t bitRate,
                             bool enabled = true)
       : JsepCodecDescription(mozilla::SdpMediaSection::kAudio, defaultPt, name,
                              clock, channels, enabled),
         mPacketSize(packetSize),
         mBitrate(bitRate),
         mMaxPlaybackRate(0),
         mForceMono(false),
-        mFECEnabled(false)
+        mFECEnabled(false),
+        mDtmfEnabled(false)
   {
   }
 
   JSEP_CODEC_CLONE(JsepAudioCodecDescription)
 
   SdpFmtpAttributeList::OpusParameters
   GetOpusParameters(const std::string& pt,
                     const SdpMediaSection& msection) const
@@ -151,16 +152,33 @@ class JsepAudioCodecDescription : public
     if (params && params->codec_type == SdpRtpmapAttributeList::kOpus) {
       result =
         static_cast<const SdpFmtpAttributeList::OpusParameters&>(*params);
     }
 
     return result;
   }
 
+  SdpFmtpAttributeList::TelephoneEventParameters
+  GetTelephoneEventParameters(const std::string& pt,
+                              const SdpMediaSection& msection) const
+  {
+    // Will contain defaults if nothing else
+    SdpFmtpAttributeList::TelephoneEventParameters result;
+    auto* params = msection.FindFmtp(pt);
+
+    if (params && params->codec_type == SdpRtpmapAttributeList::kTelephoneEvent) {
+      result =
+        static_cast<const SdpFmtpAttributeList::TelephoneEventParameters&>
+            (*params);
+    }
+
+    return result;
+  }
+
   void
   AddParametersToMSection(SdpMediaSection& msection) const override
   {
     if (mDirection == sdp::kSend) {
       return;
     }
 
     if (mName == "opus") {
@@ -170,16 +188,21 @@ class JsepAudioCodecDescription : public
         opusParams.maxplaybackrate = mMaxPlaybackRate;
       }
       if (mChannels == 2 && !mForceMono) {
         // We prefer to receive stereo, if available.
         opusParams.stereo = 1;
       }
       opusParams.useInBandFec = mFECEnabled ? 1 : 0;
       msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, opusParams));
+    } else if (mName == "telephone-event") {
+      // add the default dtmf tones
+      SdpFmtpAttributeList::TelephoneEventParameters teParams(
+          GetTelephoneEventParameters(mDefaultPt, msection));
+      msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, teParams));
     }
   }
 
   bool
   Negotiate(const std::string& pt,
             const SdpMediaSection& remoteMsection) override
   {
     JsepCodecDescription::Negotiate(pt, remoteMsection);
@@ -198,16 +221,17 @@ class JsepAudioCodecDescription : public
     return true;
   }
 
   uint32_t mPacketSize;
   uint32_t mBitrate;
   uint32_t mMaxPlaybackRate;
   bool mForceMono;
   bool mFECEnabled;
+  bool mDtmfEnabled;
 };
 
 class JsepVideoCodecDescription : public JsepCodecDescription {
  public:
   JsepVideoCodecDescription(const std::string& defaultPt,
                             const std::string& name,
                             uint32_t clock,
                             bool enabled = true)
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
@@ -2148,16 +2148,28 @@ JsepSessionImpl::SetupDefaultCodecs()
       new JsepAudioCodecDescription("8",
                                     "PCMA",
                                     8000,
                                     1,
                                     8000 / 50,   // frequency / 50
                                     8 * 8000 * 1 // 8 * frequency * channels
                                     ));
 
+  // note: because telephone-event is effectively a marker codec that indicates
+  // that dtmf rtp packets may be passed, the packetSize and bitRate fields
+  // don't make sense here.  For now, use zero. (mjf)
+  mSupportedCodecs.values.push_back(
+      new JsepAudioCodecDescription("101",
+                                    "telephone-event",
+                                    8000,
+                                    1,
+                                    0, // packetSize doesn't make sense here
+                                    0  // bitRate doesn't make sense here
+                                    ));
+
   // Supported video codecs.
   // Note: order here implies priority for building offers!
   JsepVideoCodecDescription* vp8 = new JsepVideoCodecDescription(
       "120",
       "VP8",
       90000
       );
   // Defaults for mandatory params
--- a/media/webrtc/signaling/src/jsep/JsepTrack.cpp
+++ b/media/webrtc/signaling/src/jsep/JsepTrack.cpp
@@ -339,26 +339,32 @@ JsepTrack::NegotiateCodecs(
         if (formatChanges) {
           (*formatChanges)[originalFormat] = codec->mDefaultPt;
         }
         break;
       }
     }
   }
 
-  // Find the (potential) red codec and ulpfec codec
+  // Find the (potential) red codec and ulpfec codec or telephone-event
   JsepVideoCodecDescription* red = nullptr;
   JsepVideoCodecDescription* ulpfec = nullptr;
+  JsepAudioCodecDescription* dtmf = nullptr;
+  // We can safely cast here since JsepTrack has a MediaType and only codecs
+  // that match that MediaType (kAudio or kVideo) are added.
   for (auto codec : *codecs) {
     if (codec->mName == "red") {
       red = static_cast<JsepVideoCodecDescription*>(codec);
     }
     else if (codec->mName == "ulpfec") {
       ulpfec = static_cast<JsepVideoCodecDescription*>(codec);
     }
+    else if (codec->mName == "telephone-event") {
+      dtmf = static_cast<JsepAudioCodecDescription*>(codec);
+    }
   }
   // if we have a red codec remove redundant encodings that don't exist
   if (red) {
     // Since we could have an externally specified redundant endcodings
     // list, we shouldn't simply rebuild the redundant encodings list
     // based on the current list of codecs.
     std::vector<uint8_t> unnegotiatedEncodings;
     std::swap(unnegotiatedEncodings, red->mRedundantEncodings);
@@ -381,29 +387,49 @@ JsepTrack::NegotiateCodecs(
       if (codec->mName != "red" && codec->mName != "ulpfec") {
         JsepVideoCodecDescription* videoCodec =
             static_cast<JsepVideoCodecDescription*>(codec);
         videoCodec->EnableFec();
       }
     }
   }
 
+  // Dtmf support is indicated by the existence of the telephone-event
+  // codec, and not an attribute on the particular audio codec (like in a
+  // rtcpfb attr). If we see the telephone-event codec, we enabled dtmf
+  // support on all the other audio codecs.
+  if (dtmf) {
+    for (auto codec : *codecs) {
+      JsepAudioCodecDescription* audioCodec =
+          static_cast<JsepAudioCodecDescription*>(codec);
+      audioCodec->mDtmfEnabled = true;
+    }
+  }
+
   // 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.  For now, remove all but the first codec unless the red codec
   // exists, and then we include the others per RFC 5109, section 14.2.
+  // Note: now allows keeping the telephone-event codec, if it appears, as the
+  // last codec in the list.
   if (!codecs->empty() && !red) {
+    int newSize = dtmf ? 2 : 1;
     for (size_t i = 1; i < codecs->size(); ++i) {
-      delete (*codecs)[i];
-      (*codecs)[i] = nullptr;
+      if (!dtmf || dtmf != (*codecs)[i]) {
+        delete (*codecs)[i];
+        (*codecs)[i] = nullptr;
+      }
     }
-    codecs->resize(1);
+    if (dtmf) {
+      (*codecs)[newSize-1] = dtmf;
+    }
+    codecs->resize(newSize);
   }
 }
 
 void
 JsepTrack::Negotiate(const SdpMediaSection& answer,
                      const SdpMediaSection& remote)
 {
   PtrVector<JsepCodecDescription> negotiatedCodecs;
--- a/media/webrtc/signaling/src/media-conduit/CodecConfig.h
+++ b/media/webrtc/signaling/src/media-conduit/CodecConfig.h
@@ -25,16 +25,17 @@ struct AudioCodecConfig
   int mType;
   std::string mName;
   int mFreq;
   int mPacSize;
   int mChannels;
   int mRate;
 
   bool mFECEnabled;
+  bool mDtmfEnabled;
 
   // OPUS-specific
   int mMaxPlaybackRate;
 
   /* Default constructor is not provided since as a consumer, we
    * can't decide the default configuration for the codec
    */
   explicit AudioCodecConfig(int type, std::string name,
@@ -42,16 +43,17 @@ struct AudioCodecConfig
                             int channels, int rate, bool FECEnabled)
                                                    : mType(type),
                                                      mName(name),
                                                      mFreq(freq),
                                                      mPacSize(pacSize),
                                                      mChannels(channels),
                                                      mRate(rate),
                                                      mFECEnabled(FECEnabled),
+                                                     mDtmfEnabled(false),
                                                      mMaxPlaybackRate(0)
   {
   }
 };
 
 /*
  * Minimalistic video codec configuration
  * More to be added later depending on the use-case
--- a/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
+++ b/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
@@ -72,16 +72,17 @@ JsepCodecDescToCodecConfig(const JsepCod
   *aConfig = new AudioCodecConfig(pt,
                                   desc.mName,
                                   desc.mClock,
                                   desc.mPacketSize,
                                   desc.mForceMono ? 1 : desc.mChannels,
                                   desc.mBitrate,
                                   desc.mFECEnabled);
   (*aConfig)->mMaxPlaybackRate = desc.mMaxPlaybackRate;
+  (*aConfig)->mDtmfEnabled = desc.mDtmfEnabled;
 
   return NS_OK;
 }
 
 static std::vector<JsepCodecDescription*>
 GetCodecs(const JsepTrackNegotiatedDetails& aDetails)
 {
   // We do not try to handle cases where a codec is not used on the primary
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -891,17 +891,18 @@ class ConfigureCodec {
       mH264Level(13), // minimum suggested for WebRTC spec
       mH264MaxBr(0), // Unlimited
       mH264MaxMbps(0), // Unlimited
       mVP8MaxFs(0),
       mVP8MaxFr(0),
       mUseTmmbr(false),
       mUseRemb(false),
       mUseAudioFec(false),
-      mRedUlpfecEnabled(false)
+      mRedUlpfecEnabled(false),
+      mDtmfEnabled(false)
     {
 #ifdef MOZ_WEBRTC_OMX
       // Check to see if what HW codecs are available (not in use) at this moment.
       // Note that streaming video decode can reserve a decoder
 
       // XXX See bug 1018791 Implement W3 codec reservation policy
       // Note that currently, OMXCodecReservation needs to be held by an sp<> because it puts
       // 'this' into an sp<EventListener> to talk to the resource reservation code
@@ -965,27 +966,33 @@ class ConfigureCodec {
 
       // REMB is enabled by default, but can be disabled from about:config
       branch->GetBoolPref("media.navigator.video.use_remb", &mUseRemb);
 
       branch->GetBoolPref("media.navigator.audio.use_fec", &mUseAudioFec);
 
       branch->GetBoolPref("media.navigator.video.red_ulpfec_enabled",
                           &mRedUlpfecEnabled);
+
+      // media.peerconnection.dtmf.enabled controls both sdp generation for
+      // DTMF support as well as DTMF exposure to DOM
+      branch->GetBoolPref("media.peerconnection.dtmf.enabled", &mDtmfEnabled);
     }
 
     void operator()(JsepCodecDescription* codec) const
     {
       switch (codec->mType) {
         case SdpMediaSection::kAudio:
           {
             JsepAudioCodecDescription& audioCodec =
               static_cast<JsepAudioCodecDescription&>(*codec);
             if (audioCodec.mName == "opus") {
               audioCodec.mFECEnabled = mUseAudioFec;
+            } else if (audioCodec.mName == "telephone-event") {
+              audioCodec.mEnabled = mDtmfEnabled;
             }
           }
           break;
         case SdpMediaSection::kVideo:
           {
             JsepVideoCodecDescription& videoCodec =
               static_cast<JsepVideoCodecDescription&>(*codec);
 
@@ -1048,16 +1055,17 @@ class ConfigureCodec {
     int32_t mH264MaxBr;
     int32_t mH264MaxMbps;
     int32_t mVP8MaxFs;
     int32_t mVP8MaxFr;
     bool mUseTmmbr;
     bool mUseRemb;
     bool mUseAudioFec;
     bool mRedUlpfecEnabled;
+    bool mDtmfEnabled;
 };
 
 class ConfigureRedCodec {
   public:
     explicit ConfigureRedCodec(nsCOMPtr<nsIPrefBranch>& branch,
                                std::vector<uint8_t>* redundantEncodings) :
       mRedundantEncodings(redundantEncodings)
     {
--- a/media/webrtc/signaling/src/sdp/SdpAttribute.cpp
+++ b/media/webrtc/signaling/src/sdp/SdpAttribute.cpp
@@ -1126,16 +1126,17 @@ ShouldSerializeChannels(SdpRtpmapAttribu
     case SdpRtpmapAttributeList::kPCMA:
     case SdpRtpmapAttributeList::kVP8:
     case SdpRtpmapAttributeList::kVP9:
     case SdpRtpmapAttributeList::kiLBC:
     case SdpRtpmapAttributeList::kiSAC:
     case SdpRtpmapAttributeList::kH264:
     case SdpRtpmapAttributeList::kRed:
     case SdpRtpmapAttributeList::kUlpfec:
+    case SdpRtpmapAttributeList::kTelephoneEvent:
       return false;
     case SdpRtpmapAttributeList::kOtherCodec:
       return true;
   }
   MOZ_CRASH();
 }
 
 void
--- a/media/webrtc/signaling/src/sdp/SdpAttribute.h
+++ b/media/webrtc/signaling/src/sdp/SdpAttribute.h
@@ -1082,16 +1082,17 @@ public:
     kPCMA,
     kVP8,
     kVP9,
     kiLBC,
     kiSAC,
     kH264,
     kRed,
     kUlpfec,
+    kTelephoneEvent,
     kOtherCodec
   };
 
   struct Rtpmap {
     std::string pt;
     CodecType codec;
     std::string name;
     uint32_t clock;
@@ -1167,16 +1168,19 @@ inline std::ostream& operator<<(std::ost
       os << "H264";
       break;
     case SdpRtpmapAttributeList::kRed:
       os << "red";
       break;
     case SdpRtpmapAttributeList::kUlpfec:
       os << "ulpfec";
       break;
+    case SdpRtpmapAttributeList::kTelephoneEvent:
+      os << "telephone-event";
+      break;
     default:
       MOZ_ASSERT(false);
       os << "?";
   }
   return os;
 }
 
 ///////////////////////////////////////////////////////////////////////////
@@ -1362,16 +1366,39 @@ public:
          << ";useinbandfec=" << useInBandFec;
     }
 
     unsigned int maxplaybackrate;
     unsigned int stereo;
     unsigned int useInBandFec;
   };
 
+  class TelephoneEventParameters : public Parameters
+  {
+  public:
+    TelephoneEventParameters() :
+      Parameters(SdpRtpmapAttributeList::kTelephoneEvent),
+      dtmfTones("0-15")
+    {}
+
+    virtual Parameters*
+    Clone() const override
+    {
+      return new TelephoneEventParameters(*this);
+    }
+
+    void
+    Serialize(std::ostream& os) const override
+    {
+      os << dtmfTones;
+    }
+
+    std::string dtmfTones;
+  };
+
   class Fmtp
   {
   public:
     Fmtp(const std::string& aFormat, UniquePtr<Parameters> aParameters)
         : format(aFormat),
           parameters(Move(aParameters))
     {
     }
--- a/media/webrtc/signaling/src/sdp/SdpMediaSection.cpp
+++ b/media/webrtc/signaling/src/sdp/SdpMediaSection.cpp
@@ -42,16 +42,36 @@ SdpMediaSection::SetFmtp(const SdpFmtpAt
 
   if (!found) {
     fmtps->mFmtps.push_back(fmtpToSet);
   }
 
   GetAttributeList().SetAttribute(fmtps.release());
 }
 
+void
+SdpMediaSection::RemoveFmtp(const std::string& pt)
+{
+  UniquePtr<SdpFmtpAttributeList> fmtps(new SdpFmtpAttributeList);
+
+  SdpAttributeList& attrList = GetAttributeList();
+  if (attrList.HasAttribute(SdpAttribute::kFmtpAttribute)) {
+    *fmtps = attrList.GetFmtp();
+  }
+
+  for (size_t i = 0; i < fmtps->mFmtps.size(); ++i) {
+    if (pt == fmtps->mFmtps[i].format) {
+      fmtps->mFmtps.erase(fmtps->mFmtps.begin() + i);
+      break;
+    }
+  }
+
+  attrList.SetAttribute(fmtps.release());
+}
+
 const SdpRtpmapAttributeList::Rtpmap*
 SdpMediaSection::FindRtpmap(const std::string& pt) const
 {
   auto& attrs = GetAttributeList();
   if (!attrs.HasAttribute(SdpAttribute::kRtpmapAttribute)) {
     return nullptr;
   }
 
--- a/media/webrtc/signaling/src/sdp/SdpMediaSection.h
+++ b/media/webrtc/signaling/src/sdp/SdpMediaSection.h
@@ -148,16 +148,17 @@ public:
 
   inline void SetDirection(SdpDirectionAttribute::Direction direction)
   {
     GetAttributeList().SetAttribute(new SdpDirectionAttribute(direction));
   }
 
   const SdpFmtpAttributeList::Parameters* FindFmtp(const std::string& pt) const;
   void SetFmtp(const SdpFmtpAttributeList::Fmtp& fmtp);
+  void RemoveFmtp(const std::string& pt);
   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);
--- a/media/webrtc/signaling/src/sdp/SipccSdpAttributeList.cpp
+++ b/media/webrtc/signaling/src/sdp/SipccSdpAttributeList.cpp
@@ -358,31 +358,32 @@ SipccSdpAttributeList::GetCodecType(rtp_
     case RTP_VP8:
       return SdpRtpmapAttributeList::kVP8;
     case RTP_VP9:
       return SdpRtpmapAttributeList::kVP9;
     case RTP_RED:
       return SdpRtpmapAttributeList::kRed;
     case RTP_ULPFEC:
       return SdpRtpmapAttributeList::kUlpfec;
+    case RTP_TELEPHONE_EVENT:
+      return SdpRtpmapAttributeList::kTelephoneEvent;
     case RTP_NONE:
     // Happens when sipcc doesn't know how to translate to the enum
     case RTP_CELP:
     case RTP_G726:
     case RTP_GSM:
     case RTP_G723:
     case RTP_DVI4:
     case RTP_DVI4_II:
     case RTP_LPC:
     case RTP_G728:
     case RTP_G729:
     case RTP_JPEG:
     case RTP_NV:
     case RTP_H261:
-    case RTP_AVT:
     case RTP_L16:
     case RTP_H263:
     case RTP_ILBC:
     case RTP_I420:
       return SdpRtpmapAttributeList::kOtherCodec;
   }
   MOZ_CRASH("Invalid codec type from sipcc. Probably corruption.");
 }
@@ -752,16 +753,24 @@ SipccSdpAttributeList::LoadFmtp(sdp_t* s
       case RTP_OPUS: {
         SdpFmtpAttributeList::OpusParameters* opusParameters(
             new SdpFmtpAttributeList::OpusParameters);
         opusParameters->maxplaybackrate = fmtp->maxplaybackrate;
         opusParameters->stereo = fmtp->stereo;
         opusParameters->useInBandFec = fmtp->useinbandfec;
         parameters.reset(opusParameters);
       } break;
+      case RTP_TELEPHONE_EVENT: {
+        SdpFmtpAttributeList::TelephoneEventParameters* teParameters(
+          new SdpFmtpAttributeList::TelephoneEventParameters);
+        if (strlen(fmtp->dtmf_tones) > 0) {
+          teParameters->dtmfTones = fmtp->dtmf_tones;
+        }
+        parameters.reset(teParameters);
+      } break;
       default: {
       }
     }
 
     fmtps->PushEntry(osPayloadType.str(), Move(parameters));
   }
 
   if (!fmtps->mFmtps.empty()) {
--- a/media/webrtc/signaling/src/sdp/sipcc/ccsdp.h
+++ b/media/webrtc/signaling/src/sdp/sipcc/ccsdp.h
@@ -28,17 +28,17 @@ typedef enum rtp_ptype_
     RTP_G722         = 9,
     RTP_G728         = 15,
     RTP_G729         = 18,
     RTP_JPEG         = 26,
     RTP_NV           = 28,
     RTP_H261         = 31,
     RTP_H264_P0      = 97,
     RTP_H264_P1      = 126,
-    RTP_AVT          = 101,
+    RTP_TELEPHONE_EVENT = 101,
     RTP_L16          = 102,
     RTP_H263         = 103,
     RTP_ILBC         = 116, /* used only to make an offer */
     RTP_OPUS         = 109,
     RTP_VP8          = 120,
     RTP_VP9          = 121,
     RTP_RED          = 122,
     RTP_ULPFEC       = 123,
--- a/media/webrtc/signaling/src/sdp/sipcc/sdp.h
+++ b/media/webrtc/signaling/src/sdp/sipcc/sdp.h
@@ -711,16 +711,25 @@ typedef struct sdp_fmtp {
 
     /* H.263 codec requires annex K,N and P to have values */
     uint16_t                       annex_k_val;
     uint16_t                       annex_n_val;
 
     /* RFC 5109 Section 4.2 for specifying redundant encodings */
     uint8_t              redundant_encodings[SDP_FMTP_MAX_REDUNDANT_ENCODINGS];
 
+    /* RFC 2833 Section 3.9 (4733) for specifying support DTMF tones:
+       The list of values consists of comma-separated elements, which
+       can be either a single decimal number or two decimal numbers
+       separated by a hyphen (dash), where the second number is larger
+       than the first. No whitespace is allowed between numbers or
+       hyphens. The list does not have to be sorted.
+     */
+    char                 dtmf_tones[SDP_MAX_STRING_LEN+1];
+
     /* Annex P can take one or more values in the range 1-4 . e.g P=1,3 */
     uint16_t                       annex_p_val_picture_resize; /* 1 = four; 2 = sixteenth */
     uint16_t                       annex_p_val_warp; /* 3 = half; 4=sixteenth */
 
     uint8_t                        flag;
 
   /* END - All Video related FMTP parameters */
 
--- a/media/webrtc/signaling/src/sdp/sipcc/sdp_access.c
+++ b/media/webrtc/signaling/src/sdp/sipcc/sdp_access.c
@@ -26,16 +26,17 @@ static const char* logTag = "sdp_access"
 #define SIPSDP_ATTR_ENCNAME_H264      "H264"
 #define SIPSDP_ATTR_ENCNAME_VP8       "VP8"
 #define SIPSDP_ATTR_ENCNAME_VP9       "VP9"
 #define SIPSDP_ATTR_ENCNAME_L16_256K  "L16"
 #define SIPSDP_ATTR_ENCNAME_ISAC      "ISAC"
 #define SIPSDP_ATTR_ENCNAME_OPUS      "opus"
 #define SIPSDP_ATTR_ENCNAME_RED       "red"
 #define SIPSDP_ATTR_ENCNAME_ULPFEC    "ulpfec"
+#define SIPSDP_ATTR_ENCNAME_TELEPHONE_EVENT "telephone-event"
 
 /* Function:    sdp_find_media_level
  * Description: Find and return a pointer to the specified media level,
  *              if it exists.
  *              Note: This is not an API for the application but an internal
  *              routine used by the SDP library.
  * Parameters:  sdp_p     The SDP handle returned by sdp_init_description.
  *              level       The media level to find.
@@ -1380,16 +1381,19 @@ rtp_ptype sdp_get_known_payload_type(sdp
           return (RTP_VP9);
         }
         if (cpr_strcasecmp(encname, SIPSDP_ATTR_ENCNAME_RED) == 0) {
           return (RTP_RED);
         }
         if (cpr_strcasecmp(encname, SIPSDP_ATTR_ENCNAME_ULPFEC) == 0) {
           return (RTP_ULPFEC);
         }
+        if (cpr_strcasecmp(encname, SIPSDP_ATTR_ENCNAME_TELEPHONE_EVENT) == 0) {
+          return (RTP_TELEPHONE_EVENT);
+        }
       }
     }
   }
 
   return (RTP_NONE);
 }
 
 /* Function:    sdp_get_media_payload_type
--- a/media/webrtc/signaling/src/sdp/sipcc/sdp_attr.c
+++ b/media/webrtc/signaling/src/sdp/sipcc/sdp_attr.c
@@ -448,16 +448,85 @@ static void sdp_attr_fmtp_invalid_value(
   char* param_value)
 {
   sdp_parse_error(sdp,
     "%s Warning: Invalid %s: %s specified for fmtp attribute",
     sdp->debug_str, param_name, param_value);
   sdp->conf_p->num_invalid_param++;
 }
 
+/*
+ * sdp_verify_attr_fmtp_telephone_event
+ * Helper function for verifying the telephone-event fmtp format
+ */
+static sdp_result_e sdp_verify_attr_fmtp_telephone_event(char *fmtpVal)
+{
+  size_t len = PL_strlen(fmtpVal);
+
+  // make sure the basics are good:
+  // - at least 1 character
+  // - no illegal chars
+  // - first char is a number
+  if (len < 1
+      || strspn(fmtpVal, "0123456789,-") != len
+      || PL_strstr(fmtpVal, ",,")
+      || fmtpVal[len-1] == ','
+      || !('0' <= fmtpVal[0] && fmtpVal[0] <= '9')) {
+    return SDP_INVALID_PARAMETER;
+  }
+
+  // Now that we've passed the basic sanity test, copy the string so we
+  // can tokenize and check the format of the tokens without disturbing
+  // the input string.
+  char dtmf_tones[SDP_MAX_STRING_LEN+1];
+  PL_strncpyz(dtmf_tones, fmtpVal, sizeof(dtmf_tones));
+
+  char *strtok_state;
+  char *temp = PL_strtok_r(dtmf_tones, ",", &strtok_state);
+
+  while (temp != NULL) {
+    len = PL_strlen(temp);
+    if (len > 5) {
+      // an example of a max size token is "11-15", so if the
+      // token is longer than 5 it is bad
+      return SDP_INVALID_PARAMETER;
+    }
+
+    // case where we have 1 or 2 characters, example 4 or 23
+    if (len < 3 && strspn(temp, "0123456789") != len) {
+      return SDP_INVALID_PARAMETER;
+    } else if (len >= 3) {
+      // case where we have 3-5 characters, ex 3-5, 2-33, or 10-20
+      sdp_result_e result1 = SDP_SUCCESS;
+      sdp_result_e result2 = SDP_SUCCESS;
+      uint8_t low_val;
+      uint8_t high_val;
+      low_val = (uint8_t)sdp_getnextnumtok(temp, (const char **)&temp,
+                                           "-", &result1);
+      high_val = (uint8_t)sdp_getnextnumtok(temp, (const char **)&temp,
+                                            "-", &result2);
+      if (temp[0] // we don't want to find a second hyphen
+          || result1 != SDP_SUCCESS
+          || result2 != SDP_SUCCESS) {
+        return SDP_INVALID_PARAMETER;
+      }
+
+      if (low_val > 99
+          || high_val > 99
+          || high_val <= low_val) {
+        return SDP_INVALID_PARAMETER;
+      }
+    }
+
+    temp=PL_strtok_r(NULL, ",", &strtok_state);
+  }
+
+  return SDP_SUCCESS;
+}
+
 /* Note:  The fmtp attribute formats currently handled are:
  *        fmtp:<payload type> <event>,<event>...
  *        fmtp:<payload_type> [annexa=yes/no] [annexb=yes/no] [bitrate=<value>]
  *        [QCIF =<value>] [CIF =<value>] [MaxBR = <value>] one or more
  *        Other FMTP params as per H.263, H.263+, H.264 codec support.
  *        Note -"value" is a numeric value > 0 and each event is a
  *        single number or a range separated by a '-'.
  *        Example:  fmtp:101 1,3-15,20
@@ -1796,20 +1865,22 @@ sdp_result_e sdp_parse_attr_fmtp (sdp_t 
                         temp == strtoul_end || strtoul_result > USHRT_MAX) {
                         continue;
                     }
                     fmtp_p->redundant_encodings[iter++] =
                         (uint8_t)strtoul_result;
                     temp=PL_strtok_r(NULL, "/", &strtok_state);
                 }
             } /* if (temp) */
-        } else {
+        } else if (SDP_SUCCESS == sdp_verify_attr_fmtp_telephone_event(tmp)) {
           // XXX Note that DTMF fmtp will fall into here:
           // a=fmtp:101 0-15 (or 0-15,NN,NN etc)
-
+          sstrncpy(fmtp_p->dtmf_tones , tmp, sizeof(fmtp_p->dtmf_tones));
+          codec_info_found = TRUE;
+        } else {
           // unknown parameter - eat chars until ';'
           CSFLogDebug(logTag, "%s Unknown fmtp type (%s) - ignoring", __FUNCTION__,
                       tmp);
           fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), "; \t",
                                        &result1);
           if (result1 != SDP_SUCCESS) {
             fmtp_ptr = sdp_getnextstrtok(fmtp_ptr, tmp, sizeof(tmp), " \t", &result1);
             if (result1 != SDP_SUCCESS) {
--- a/media/webrtc/signaling/test/jsep_session_unittest.cpp
+++ b/media/webrtc/signaling/test/jsep_session_unittest.cpp
@@ -635,16 +635,17 @@ protected:
           msidAttr += pairs[i].mSending->GetStreamId();
           msidAttr += " ";
           msidAttr += pairs[i].mSending->GetTrackId();
           ASSERT_NE(std::string::npos, answer.find(msidAttr))
             << "Did not find " << msidAttr << " in offer";
         }
       }
     }
+    std::cerr << "OFFER pairs:" << std::endl;
     DumpTrackPairs(mSessionOff);
   }
 
   void
   SetRemoteAnswer(const std::string& answer, uint32_t checkFlags = ALL_CHECKS)
   {
     nsresult rv = mSessionOff.SetRemoteDescription(kJsepSdpAnswer, answer);
     if (checkFlags & CHECK_SUCCESS) {
@@ -672,16 +673,17 @@ protected:
           msidAttr += pairs[i].mReceiving->GetStreamId();
           msidAttr += " ";
           msidAttr += pairs[i].mReceiving->GetTrackId();
           ASSERT_NE(std::string::npos, answer.find(msidAttr))
             << "Did not find " << msidAttr << " in answer";
         }
       }
     }
+    std::cerr << "ANSWER pairs:" << std::endl;
     DumpTrackPairs(mSessionAns);
   }
 
   typedef enum {
     RTP = 1,
     RTCP = 2
   } ComponentType;
 
@@ -954,16 +956,28 @@ protected:
     UniquePtr<Sdp> parsed(Parse(*sdp));
     ASSERT_TRUE(parsed.get());
     ASSERT_LT(level, parsed->GetMediaSectionCount());
     SdpHelper::DisableMsection(parsed.get(), &parsed->GetMediaSection(level));
     (*sdp) = parsed->ToString();
   }
 
   void
+  ReplaceInSdp(std::string* sdp,
+               const char* searchStr,
+               const char* replaceStr) const
+  {
+    if (searchStr[0] == '\0') return;
+    size_t pos;
+    while ((pos = sdp->find(searchStr)) != std::string::npos) {
+      sdp->replace(pos, strlen(searchStr), replaceStr);
+    }
+  }
+
+  void
   ValidateDisabledMSection(const SdpMediaSection* msection)
   {
     ASSERT_EQ(1U, msection->GetFormats().size());
     // Maybe validate that no attributes are present except rtpmap and
     // inactive? How?
     ASSERT_EQ(SdpDirectionAttribute::kInactive,
               msection->GetDirectionAttribute().mValue);
     if (msection->GetMediaType() == SdpMediaSection::kAudio) {
@@ -998,19 +1012,26 @@ protected:
   void
   DumpTrack(const JsepTrack& track)
   {
     const JsepTrackNegotiatedDetails* details = track.GetNegotiatedDetails();
     std::cerr << "  type=" << track.GetMediaType() << std::endl;
     std::cerr << "  encodings=" << std::endl;
     for (size_t i = 0; i < details->GetEncodingCount(); ++i) {
       const JsepTrackEncoding& encoding = details->GetEncoding(i);
-      std::cerr << "    id=" << encoding.mRid;
+      std::cerr << "    id=" << encoding.mRid << std::endl;
       for (const JsepCodecDescription* codec : encoding.GetCodecs()) {
-        std::cerr << "      " << codec->mName << std::endl;
+        std::cerr << "      " << codec->mName
+                  << " enabled(" << (codec->mEnabled?"yes":"no") << ")";
+        if (track.GetMediaType() == SdpMediaSection::kAudio) {
+          const JsepAudioCodecDescription* audioCodec =
+              static_cast<const JsepAudioCodecDescription*>(codec);
+          std::cerr << " dtmf(" << (audioCodec->mDtmfEnabled?"yes":"no") << ")";
+        }
+        std::cerr << std::endl;
       }
     }
   }
 
   void
   DumpTrackPairs(const JsepSessionImpl& session)
   {
     auto pairs = mSessionAns.GetNegotiatedTrackPairs();
@@ -1942,16 +1963,89 @@ TEST_P(JsepSessionTest, RenegotiationAut
   auto newOffererPairs = mSessionOff.GetNegotiatedTrackPairs();
 
   ASSERT_EQ(offererPairs.size(), newOffererPairs.size());
   for (size_t i = 0; i < offererPairs.size(); ++i) {
     ASSERT_TRUE(Equals(offererPairs[i], newOffererPairs[i]));
   }
 }
 
+TEST_P(JsepSessionTest, RenegotiationOffererDisablesTelephoneEvent)
+{
+  AddTracks(mSessionOff);
+  AddTracks(mSessionAns);
+  OfferAnswer();
+
+  auto offererPairs = GetTrackPairsByLevel(mSessionOff);
+
+  // check all the audio tracks to make sure they have 2 codecs (109 and 101),
+  // and dtmf is enabled on all audio tracks
+  for (size_t i = 0; i < offererPairs.size(); ++i) {
+    std::vector<JsepTrack*> tracks;
+    tracks.push_back(offererPairs[i].mSending.get());
+    tracks.push_back(offererPairs[i].mReceiving.get());
+    for (JsepTrack *track : tracks) {
+      if (track->GetMediaType() != SdpMediaSection::kAudio) {
+        continue;
+      }
+      const JsepTrackNegotiatedDetails* details = track->GetNegotiatedDetails();
+      ASSERT_EQ(1U, details->GetEncodingCount());
+      const JsepTrackEncoding& encoding = details->GetEncoding(0);
+      ASSERT_EQ(2U, encoding.GetCodecs().size());
+      ASSERT_TRUE(encoding.HasFormat("109"));
+      ASSERT_TRUE(encoding.HasFormat("101"));
+      for (JsepCodecDescription* codec: encoding.GetCodecs()) {
+        ASSERT_TRUE(codec);
+        // we can cast here because we've already checked for audio track
+        JsepAudioCodecDescription *audioCodec =
+            static_cast<JsepAudioCodecDescription*>(codec);
+        ASSERT_TRUE(audioCodec->mDtmfEnabled);
+      }
+    }
+  }
+
+  std::string offer = CreateOffer();
+  ReplaceInSdp(&offer, " 109 101 ", " 109 ");
+  ReplaceInSdp(&offer, "a=fmtp:101 0-15\r\n", "");
+  ReplaceInSdp(&offer, "a=rtpmap:101 telephone-event/8000/1\r\n", "");
+  std::cerr << "modified OFFER: " << offer << std::endl;
+
+  SetLocalOffer(offer);
+  SetRemoteOffer(offer);
+  AddTracks(mSessionAns);
+  std::string answer = CreateAnswer();
+  SetLocalAnswer(answer);
+  SetRemoteAnswer(answer);
+
+  auto newOffererPairs = GetTrackPairsByLevel(mSessionOff);
+
+  // check all the audio tracks to make sure they have 1 codec (109),
+  // and dtmf is disabled on all audio tracks
+  for (size_t i = 0; i < newOffererPairs.size(); ++i) {
+    std::vector<JsepTrack*> tracks;
+    tracks.push_back(newOffererPairs[i].mSending.get());
+    tracks.push_back(newOffererPairs[i].mReceiving.get());
+    for (JsepTrack* track : tracks) {
+      if (track->GetMediaType() != SdpMediaSection::kAudio) {
+        continue;
+      }
+      const JsepTrackNegotiatedDetails* details = track->GetNegotiatedDetails();
+      ASSERT_EQ(1U, details->GetEncodingCount());
+      const JsepTrackEncoding& encoding = details->GetEncoding(0);
+      ASSERT_EQ(1U, encoding.GetCodecs().size());
+      ASSERT_TRUE(encoding.HasFormat("109"));
+      // we can cast here because we've already checked for audio track
+      JsepAudioCodecDescription *audioCodec =
+          static_cast<JsepAudioCodecDescription*>(encoding.GetCodecs()[0]);
+      ASSERT_TRUE(audioCodec);
+      ASSERT_FALSE(audioCodec->mDtmfEnabled);
+    }
+  }
+}
+
 // Tests behavior when the answerer does not use msid in the initial exchange,
 // but does on renegotiation.
 TEST_P(JsepSessionTest, RenegotiationAnswererEnablesMsid)
 {
   AddTracks(mSessionOff);
   std::string offer = CreateOffer();
   SetLocalOffer(offer);
   SetRemoteOffer(offer);
@@ -2814,17 +2908,17 @@ TEST_F(JsepSessionTest, CreateOfferNoDat
 
   ASSERT_EQ(2U, outputSdp->GetMediaSectionCount());
   ASSERT_EQ(SdpMediaSection::kAudio,
             outputSdp->GetMediaSection(0).GetMediaType());
   ASSERT_EQ(SdpMediaSection::kVideo,
             outputSdp->GetMediaSection(1).GetMediaType());
 }
 
-TEST_F(JsepSessionTest, ValidateOfferedCodecParams)
+TEST_F(JsepSessionTest, ValidateOfferedVideoCodecParams)
 {
   types.push_back(SdpMediaSection::kAudio);
   types.push_back(SdpMediaSection::kVideo);
 
   RefPtr<JsepTrack> msta(
       new JsepTrack(SdpMediaSection::kAudio, "offerer_stream", "a1"));
   mSessionOff.AddTrack(msta);
   RefPtr<JsepTrack> mstv1(
@@ -2941,16 +3035,98 @@ TEST_F(JsepSessionTest, ValidateOfferedC
   ASSERT_EQ(5U, parsed_red_params.encodings.size());
   ASSERT_EQ(120, parsed_red_params.encodings[0]);
   ASSERT_EQ(121, parsed_red_params.encodings[1]);
   ASSERT_EQ(126, parsed_red_params.encodings[2]);
   ASSERT_EQ(97, parsed_red_params.encodings[3]);
   ASSERT_EQ(123, parsed_red_params.encodings[4]);
 }
 
+TEST_F(JsepSessionTest, ValidateOfferedAudioCodecParams)
+{
+  types.push_back(SdpMediaSection::kAudio);
+  types.push_back(SdpMediaSection::kVideo);
+
+  RefPtr<JsepTrack> msta(
+      new JsepTrack(SdpMediaSection::kAudio, "offerer_stream", "a1"));
+  mSessionOff.AddTrack(msta);
+  RefPtr<JsepTrack> mstv1(
+      new JsepTrack(SdpMediaSection::kVideo, "offerer_stream", "v2"));
+  mSessionOff.AddTrack(mstv1);
+
+  std::string offer = CreateOffer();
+
+  UniquePtr<Sdp> outputSdp(Parse(offer));
+  ASSERT_TRUE(!!outputSdp);
+
+  ASSERT_EQ(2U, outputSdp->GetMediaSectionCount());
+  auto& audio_section = outputSdp->GetMediaSection(0);
+  ASSERT_EQ(SdpMediaSection::kAudio, audio_section.GetMediaType());
+  auto& audio_attrs = audio_section.GetAttributeList();
+  ASSERT_EQ(SdpDirectionAttribute::kSendrecv, audio_attrs.GetDirection());
+  ASSERT_EQ(5U, audio_section.GetFormats().size());
+  ASSERT_EQ("109", audio_section.GetFormats()[0]);
+  ASSERT_EQ("9", audio_section.GetFormats()[1]);
+  ASSERT_EQ("0", audio_section.GetFormats()[2]);
+  ASSERT_EQ("8", audio_section.GetFormats()[3]);
+  ASSERT_EQ("101", audio_section.GetFormats()[4]);
+
+  // Validate rtpmap
+  ASSERT_TRUE(audio_attrs.HasAttribute(SdpAttribute::kRtpmapAttribute));
+  auto& rtpmaps = audio_attrs.GetRtpmap();
+  ASSERT_TRUE(rtpmaps.HasEntry("109"));
+  ASSERT_TRUE(rtpmaps.HasEntry("9"));
+  ASSERT_TRUE(rtpmaps.HasEntry("0"));
+  ASSERT_TRUE(rtpmaps.HasEntry("8"));
+  ASSERT_TRUE(rtpmaps.HasEntry("101"));
+
+  auto& opus_entry = rtpmaps.GetEntry("109");
+  auto& g722_entry = rtpmaps.GetEntry("9");
+  auto& pcmu_entry = rtpmaps.GetEntry("0");
+  auto& pcma_entry = rtpmaps.GetEntry("8");
+  auto& telephone_event_entry = rtpmaps.GetEntry("101");
+
+  ASSERT_EQ("opus", opus_entry.name);
+  ASSERT_EQ("G722", g722_entry.name);
+  ASSERT_EQ("PCMU", pcmu_entry.name);
+  ASSERT_EQ("PCMA", pcma_entry.name);
+  ASSERT_EQ("telephone-event", telephone_event_entry.name);
+
+  // Validate fmtps
+  ASSERT_TRUE(audio_attrs.HasAttribute(SdpAttribute::kFmtpAttribute));
+  auto& fmtps = audio_attrs.GetFmtp().mFmtps;
+
+  ASSERT_EQ(2U, fmtps.size());
+
+  // opus
+  const SdpFmtpAttributeList::Parameters* opus_params =
+    audio_section.FindFmtp("109");
+  ASSERT_TRUE(opus_params);
+  ASSERT_EQ(SdpRtpmapAttributeList::kOpus, opus_params->codec_type);
+
+  auto& parsed_opus_params =
+      *static_cast<const SdpFmtpAttributeList::OpusParameters*>(opus_params);
+
+  ASSERT_EQ((uint32_t)48000, parsed_opus_params.maxplaybackrate);
+  ASSERT_EQ((uint32_t)1, parsed_opus_params.stereo);
+  ASSERT_EQ((uint32_t)0, parsed_opus_params.useInBandFec);
+
+  // dtmf
+  const SdpFmtpAttributeList::Parameters* dtmf_params =
+    audio_section.FindFmtp("101");
+  ASSERT_TRUE(dtmf_params);
+  ASSERT_EQ(SdpRtpmapAttributeList::kTelephoneEvent, dtmf_params->codec_type);
+
+  auto& parsed_dtmf_params =
+      *static_cast<const SdpFmtpAttributeList::TelephoneEventParameters*>
+          (dtmf_params);
+
+  ASSERT_EQ("0-15", parsed_dtmf_params.dtmfTones);
+}
+
 TEST_F(JsepSessionTest, ValidateAnsweredCodecParams)
 {
   // TODO(bug 1099351): Once fixed, we can allow red in this offer,
   // which will also cause multiple codecs in answer.  For now,
   // red/ulpfec for video are behind a pref to mitigate potential for
   // errors.
   SetCodecEnabled(mSessionOff, "red", false);
   for (auto i = mSessionAns.Codecs().begin(); i != mSessionAns.Codecs().end();
--- a/media/webrtc/signaling/test/jsep_track_unittest.cpp
+++ b/media/webrtc/signaling/test/jsep_track_unittest.cpp
@@ -27,23 +27,30 @@
 namespace mozilla {
 
 class JsepTrackTest : public ::testing::Test
 {
   public:
     JsepTrackTest() {}
 
     std::vector<JsepCodecDescription*>
-    MakeCodecs(bool addFecCodecs = false, bool preferRed = false) const
+    MakeCodecs(bool addFecCodecs = false,
+               bool preferRed = false,
+               bool addDtmfCodec = false) const
     {
       std::vector<JsepCodecDescription*> results;
       results.push_back(
           new JsepAudioCodecDescription("1", "opus", 48000, 2, 960, 40000));
       results.push_back(
           new JsepAudioCodecDescription("9", "G722", 8000, 1, 320, 64000));
+      if (addDtmfCodec) {
+        results.push_back(
+            new JsepAudioCodecDescription("101", "telephone-event",
+                                          8000, 1, 0, 0));
+      }
 
       JsepVideoCodecDescription* red = nullptr;
       if (addFecCodecs && preferRed) {
         red = new JsepVideoCodecDescription(
             "122",
             "red",
             90000
             );
@@ -238,32 +245,54 @@ class JsepTrackTest : public ::testing::
       CheckEncodingCount(expected, mSendOff, mRecvAns);
     }
 
     void CheckAnsEncodingCount(size_t expected) const
     {
       CheckEncodingCount(expected, mSendAns, mRecvOff);
     }
 
+    const JsepCodecDescription*
+    GetCodec(const JsepTrack& track,
+             SdpMediaSection::MediaType type,
+             size_t expectedSize,
+             size_t codecIndex) const
+    {
+      if (!track.GetNegotiatedDetails() ||
+          track.GetNegotiatedDetails()->GetEncodingCount() != 1U ||
+          track.GetMediaType() != type) {
+        return nullptr;
+      }
+      const std::vector<JsepCodecDescription*>& codecs =
+        track.GetNegotiatedDetails()->GetEncoding(0).GetCodecs();
+      // it should not be possible for codecs to have a different type
+      // than the track, but we'll check the codec here just in case.
+      if (codecs.size() != expectedSize || codecIndex >= expectedSize ||
+          codecs[codecIndex]->mType != type) {
+        return nullptr;
+      }
+      return codecs[codecIndex];
+    }
+
     const JsepVideoCodecDescription*
     GetVideoCodec(const JsepTrack& track,
                   size_t expectedSize = 1,
                   size_t codecIndex = 0) const
     {
-      if (!track.GetNegotiatedDetails() ||
-          track.GetNegotiatedDetails()->GetEncodingCount() != 1U) {
-        return nullptr;
-      }
-      const std::vector<JsepCodecDescription*>& codecs =
-        track.GetNegotiatedDetails()->GetEncoding(0).GetCodecs();
-      if (codecs.size() != expectedSize ||
-          codecs[codecIndex]->mType != SdpMediaSection::kVideo) {
-        return nullptr;
-      }
-      return static_cast<const JsepVideoCodecDescription*>(codecs[0]);
+      return static_cast<const JsepVideoCodecDescription*>
+        (GetCodec(track, SdpMediaSection::kVideo, expectedSize, codecIndex));
+    }
+
+    const JsepAudioCodecDescription*
+    GetAudioCodec(const JsepTrack& track,
+                  size_t expectedSize = 1,
+                  size_t codecIndex = 0) const
+    {
+      return static_cast<const JsepAudioCodecDescription*>
+        (GetCodec(track, SdpMediaSection::kAudio, expectedSize, codecIndex));
     }
 
     void CheckOtherFbsSize(const JsepTrack& track, size_t expected) const
     {
       const JsepVideoCodecDescription* videoCodec = GetVideoCodec(track);
       ASSERT_NE(videoCodec, nullptr);
       ASSERT_EQ(videoCodec->mOtherFbTypes.size(), expected);
     }
@@ -396,16 +425,349 @@ TEST_F(JsepTrackTest, AudioNegotiation)
 TEST_F(JsepTrackTest, VideoNegotiation)
 {
   Init(SdpMediaSection::kVideo);
   OfferAnswer();
   CheckOffEncodingCount(1);
   CheckAnsEncodingCount(1);
 }
 
+class CheckForCodecType
+{
+public:
+  explicit CheckForCodecType(SdpMediaSection::MediaType type,
+                             bool *result) :
+    mResult(result),
+    mType(type) {}
+
+  void operator()(JsepCodecDescription* codec) {
+    if (codec->mType == mType) {
+      *mResult = true;
+    }
+  }
+
+private:
+  bool *mResult;
+  SdpMediaSection::MediaType mType;
+};
+
+TEST_F(JsepTrackTest, CheckForMismatchedAudioCodecAndVideoTrack)
+{
+  PtrVector<JsepCodecDescription> offerCodecs;
+
+  // make codecs including telephone-event (an audio codec)
+  offerCodecs.values = MakeCodecs(false, false, true);
+  RefPtr<JsepTrack> videoTrack = new JsepTrack(SdpMediaSection::kVideo,
+                                               "stream_id",
+                                               "track_id",
+                                               sdp::kSend);
+  // populate codecs and then make sure we don't have any audio codecs
+  // in the video track
+  videoTrack->PopulateCodecs(offerCodecs.values);
+
+  bool found = false;
+  videoTrack->ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found));
+  ASSERT_FALSE(found);
+
+  found = false;
+  videoTrack->ForEachCodec(CheckForCodecType(SdpMediaSection::kVideo, &found));
+  ASSERT_TRUE(found); // for sanity, make sure we did find video codecs
+}
+
+TEST_F(JsepTrackTest, CheckVideoTrackWithHackedDtmfSdp)
+{
+  Init(SdpMediaSection::kVideo);
+  CreateOffer();
+  // make sure we don't find sdp containing telephone-event in video track
+  ASSERT_EQ(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
+            std::string::npos);
+  // force audio codec telephone-event into video m= section of offer
+  GetOffer().AddCodec("101", "telephone-event", 8000, 1);
+  // make sure we _do_ find sdp containing telephone-event in video track
+  ASSERT_NE(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
+            std::string::npos);
+
+  CreateAnswer();
+  // make sure we don't find sdp containing telephone-event in video track
+  ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
+            std::string::npos);
+  // force audio codec telephone-event into video m= section of answer
+  GetAnswer().AddCodec("101", "telephone-event", 8000, 1);
+  // make sure we _do_ find sdp containing telephone-event in video track
+  ASSERT_NE(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
+            std::string::npos);
+
+  Negotiate();
+  SanityCheck();
+
+  CheckOffEncodingCount(1);
+  CheckAnsEncodingCount(1);
+
+  ASSERT_TRUE(mSendOff.get());
+  ASSERT_TRUE(mRecvOff.get());
+  ASSERT_TRUE(mSendAns.get());
+  ASSERT_TRUE(mRecvAns.get());
+
+  // make sure we still don't find any audio codecs in the video track after
+  // hacking the sdp
+  bool found = false;
+  mSendOff->ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found));
+  ASSERT_FALSE(found);
+  mRecvOff->ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found));
+  ASSERT_FALSE(found);
+  mSendAns->ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found));
+  ASSERT_FALSE(found);
+  mRecvAns->ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found));
+  ASSERT_FALSE(found);
+}
+
+TEST_F(JsepTrackTest, AudioNegotiationOffererDtmf)
+{
+  mOffCodecs.values = MakeCodecs(false, false, true);
+  mAnsCodecs.values = MakeCodecs(false, false, false);
+
+  InitTracks(SdpMediaSection::kAudio);
+  InitSdp(SdpMediaSection::kAudio);
+  OfferAnswer();
+
+  CheckOffEncodingCount(1);
+  CheckAnsEncodingCount(1);
+
+  ASSERT_NE(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
+            std::string::npos);
+  ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
+            std::string::npos);
+
+  ASSERT_NE(mOffer->ToString().find("a=fmtp:101 0-15"), std::string::npos);
+  ASSERT_EQ(mAnswer->ToString().find("a=fmtp:101"), std::string::npos);
+
+  const JsepAudioCodecDescription* track = nullptr;
+  ASSERT_TRUE((track = GetAudioCodec(*mSendOff)));
+  ASSERT_EQ("1", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mRecvOff)));
+  ASSERT_EQ("1", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mSendAns)));
+  ASSERT_EQ("1", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mRecvAns)));
+  ASSERT_EQ("1", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, AudioNegotiationAnswererDtmf)
+{
+  mOffCodecs.values = MakeCodecs(false, false, false);
+  mAnsCodecs.values = MakeCodecs(false, false, true);
+
+  InitTracks(SdpMediaSection::kAudio);
+  InitSdp(SdpMediaSection::kAudio);
+  OfferAnswer();
+
+  CheckOffEncodingCount(1);
+  CheckAnsEncodingCount(1);
+
+  ASSERT_EQ(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
+            std::string::npos);
+  ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
+            std::string::npos);
+
+  ASSERT_EQ(mOffer->ToString().find("a=fmtp:101 0-15"), std::string::npos);
+  ASSERT_EQ(mAnswer->ToString().find("a=fmtp:101"), std::string::npos);
+
+  const JsepAudioCodecDescription* track = nullptr;
+  ASSERT_TRUE((track = GetAudioCodec(*mSendOff)));
+  ASSERT_EQ("1", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mRecvOff)));
+  ASSERT_EQ("1", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mSendAns)));
+  ASSERT_EQ("1", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mRecvAns)));
+  ASSERT_EQ("1", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, AudioNegotiationOffererAnswererDtmf)
+{
+  mOffCodecs.values = MakeCodecs(false, false, true);
+  mAnsCodecs.values = MakeCodecs(false, false, true);
+
+  InitTracks(SdpMediaSection::kAudio);
+  InitSdp(SdpMediaSection::kAudio);
+  OfferAnswer();
+
+  CheckOffEncodingCount(1);
+  CheckAnsEncodingCount(1);
+
+  ASSERT_NE(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
+            std::string::npos);
+  ASSERT_NE(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
+            std::string::npos);
+
+  ASSERT_NE(mOffer->ToString().find("a=fmtp:101 0-15"), std::string::npos);
+  ASSERT_NE(mAnswer->ToString().find("a=fmtp:101 0-15"), std::string::npos);
+
+  const JsepAudioCodecDescription* track = nullptr;
+  ASSERT_TRUE((track = GetAudioCodec(*mSendOff, 2)));
+  ASSERT_EQ("1", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mRecvOff, 2)));
+  ASSERT_EQ("1", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mSendAns, 2)));
+  ASSERT_EQ("1", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mRecvAns, 2)));
+  ASSERT_EQ("1", track->mDefaultPt);
+
+  ASSERT_TRUE((track = GetAudioCodec(*mSendOff, 2, 1)));
+  ASSERT_EQ("101", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mRecvOff, 2, 1)));
+  ASSERT_EQ("101", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mSendAns, 2, 1)));
+  ASSERT_EQ("101", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mRecvAns, 2, 1)));
+  ASSERT_EQ("101", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, AudioNegotiationDtmfOffererNoFmtpAnswererFmtp)
+{
+  mOffCodecs.values = MakeCodecs(false, false, true);
+  mAnsCodecs.values = MakeCodecs(false, false, true);
+
+  InitTracks(SdpMediaSection::kAudio);
+  InitSdp(SdpMediaSection::kAudio);
+
+  CreateOffer();
+  GetOffer().RemoveFmtp("101");
+
+  CreateAnswer();
+
+  Negotiate();
+  SanityCheck();
+
+  CheckOffEncodingCount(1);
+  CheckAnsEncodingCount(1);
+
+  ASSERT_NE(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
+            std::string::npos);
+  ASSERT_NE(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
+            std::string::npos);
+
+  ASSERT_EQ(mOffer->ToString().find("a=fmtp:101"), std::string::npos);
+  ASSERT_NE(mAnswer->ToString().find("a=fmtp:101 0-15"), std::string::npos);
+
+  const JsepAudioCodecDescription* track = nullptr;
+  ASSERT_TRUE((track = GetAudioCodec(*mSendOff, 2)));
+  ASSERT_EQ("1", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mRecvOff, 2)));
+  ASSERT_EQ("1", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mSendAns, 2)));
+  ASSERT_EQ("1", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mRecvAns, 2)));
+  ASSERT_EQ("1", track->mDefaultPt);
+
+  ASSERT_TRUE((track = GetAudioCodec(*mSendOff, 2, 1)));
+  ASSERT_EQ("101", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mRecvOff, 2, 1)));
+  ASSERT_EQ("101", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mSendAns, 2, 1)));
+  ASSERT_EQ("101", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mRecvAns, 2, 1)));
+  ASSERT_EQ("101", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, AudioNegotiationDtmfOffererFmtpAnswererNoFmtp)
+{
+  mOffCodecs.values = MakeCodecs(false, false, true);
+  mAnsCodecs.values = MakeCodecs(false, false, true);
+
+  InitTracks(SdpMediaSection::kAudio);
+  InitSdp(SdpMediaSection::kAudio);
+
+  CreateOffer();
+
+  CreateAnswer();
+  GetAnswer().RemoveFmtp("101");
+
+  Negotiate();
+  SanityCheck();
+
+  CheckOffEncodingCount(1);
+  CheckAnsEncodingCount(1);
+
+  ASSERT_NE(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
+            std::string::npos);
+  ASSERT_NE(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
+            std::string::npos);
+
+  ASSERT_NE(mOffer->ToString().find("a=fmtp:101 0-15"), std::string::npos);
+  ASSERT_EQ(mAnswer->ToString().find("a=fmtp:101"), std::string::npos);
+
+  const JsepAudioCodecDescription* track = nullptr;
+  ASSERT_TRUE((track = GetAudioCodec(*mSendOff, 2)));
+  ASSERT_EQ("1", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mRecvOff, 2)));
+  ASSERT_EQ("1", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mSendAns, 2)));
+  ASSERT_EQ("1", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mRecvAns, 2)));
+  ASSERT_EQ("1", track->mDefaultPt);
+
+  ASSERT_TRUE((track = GetAudioCodec(*mSendOff, 2, 1)));
+  ASSERT_EQ("101", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mRecvOff, 2, 1)));
+  ASSERT_EQ("101", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mSendAns, 2, 1)));
+  ASSERT_EQ("101", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mRecvAns, 2, 1)));
+  ASSERT_EQ("101", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, AudioNegotiationDtmfOffererNoFmtpAnswererNoFmtp)
+{
+  mOffCodecs.values = MakeCodecs(false, false, true);
+  mAnsCodecs.values = MakeCodecs(false, false, true);
+
+  InitTracks(SdpMediaSection::kAudio);
+  InitSdp(SdpMediaSection::kAudio);
+
+  CreateOffer();
+  GetOffer().RemoveFmtp("101");
+
+  CreateAnswer();
+  GetAnswer().RemoveFmtp("101");
+
+  Negotiate();
+  SanityCheck();
+
+  CheckOffEncodingCount(1);
+  CheckAnsEncodingCount(1);
+
+  ASSERT_NE(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
+            std::string::npos);
+  ASSERT_NE(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
+            std::string::npos);
+
+  ASSERT_EQ(mOffer->ToString().find("a=fmtp:101"), std::string::npos);
+  ASSERT_EQ(mAnswer->ToString().find("a=fmtp:101"), std::string::npos);
+
+  const JsepAudioCodecDescription* track = nullptr;
+  ASSERT_TRUE((track = GetAudioCodec(*mSendOff, 2)));
+  ASSERT_EQ("1", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mRecvOff, 2)));
+  ASSERT_EQ("1", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mSendAns, 2)));
+  ASSERT_EQ("1", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mRecvAns, 2)));
+  ASSERT_EQ("1", track->mDefaultPt);
+
+  ASSERT_TRUE((track = GetAudioCodec(*mSendOff, 2, 1)));
+  ASSERT_EQ("101", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mRecvOff, 2, 1)));
+  ASSERT_EQ("101", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mSendAns, 2, 1)));
+  ASSERT_EQ("101", track->mDefaultPt);
+  ASSERT_TRUE((track = GetAudioCodec(*mRecvAns, 2, 1)));
+  ASSERT_EQ("101", track->mDefaultPt);
+}
+
 TEST_F(JsepTrackTest, VideoNegotationOffererFEC)
 {
   mOffCodecs.values = MakeCodecs(true);
   mAnsCodecs.values = MakeCodecs(false);
 
   InitTracks(SdpMediaSection::kVideo);
   InitSdp(SdpMediaSection::kVideo);
   OfferAnswer();
--- a/media/webrtc/signaling/test/sdp_unittests.cpp
+++ b/media/webrtc/signaling/test/sdp_unittests.cpp
@@ -995,16 +995,32 @@ class NewSdpTest : public ::testing::Tes
                      const std::string& first_parameter,
                      const std::string& extra = "") const {
       ASSERT_EQ(pt, feedback.pt);
       ASSERT_EQ(type, feedback.type);
       ASSERT_EQ(first_parameter, feedback.parameter);
       ASSERT_EQ(extra, feedback.extra);
     }
 
+    void CheckDtmfFmtp(const std::string& expectedDtmfTones) const {
+      ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+                  SdpAttribute::kFmtpAttribute));
+      auto audio_format_params =
+          mSdp->GetMediaSection(0).GetAttributeList().GetFmtp().mFmtps;
+      ASSERT_EQ(2U, audio_format_params.size());
+
+      ASSERT_EQ("101", audio_format_params[1].format);
+      ASSERT_TRUE(!!audio_format_params[1].parameters);
+      const SdpFmtpAttributeList::TelephoneEventParameters* te_parameters =
+        static_cast<SdpFmtpAttributeList::TelephoneEventParameters*>(
+            audio_format_params[1].parameters.get());
+      ASSERT_NE(0U, te_parameters->dtmfTones.size());
+      ASSERT_EQ(expectedDtmfTones, te_parameters->dtmfTones);
+    }
+
     void CheckSerialize(const std::string& expected,
                         const SdpAttribute& attr) const {
       std::stringstream str;
       attr.Serialize(str);
       ASSERT_EQ(expected, str.str());
     }
 
     SipccSdpParser mParser;
@@ -1206,17 +1222,17 @@ const std::string kBasicAudioVideoOffer 
 "a=rtpmap:109 opus/48000/2" CRLF
 "a=fmtp:109 maxplaybackrate=32000;stereo=1" CRLF
 "a=ptime:20" CRLF
 "a=maxptime:20" CRLF
 "a=rtpmap:9 G722/8000" CRLF
 "a=rtpmap:0 PCMU/8000" CRLF
 "a=rtpmap:8 PCMA/8000" CRLF
 "a=rtpmap:101 telephone-event/8000" CRLF
-"a=fmtp:101 0-15" CRLF
+"a=fmtp:101 0-15,66,32-34,67" CRLF
 "a=ice-ufrag:00000000" CRLF
 "a=ice-pwd:0000000000000000000000000000000" CRLF
 "a=sendonly" CRLF
 "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level" CRLF
 "a=setup:actpass" CRLF
 "a=rtcp-mux" CRLF
 "a=msid:stream track" CRLF
 "a=candidate:0 1 UDP 2130379007 10.0.0.36 62453 typ host" CRLF
@@ -1274,16 +1290,51 @@ const std::string kBasicAudioVideoOffer 
 "a=ice-options:foo bar" CRLF
 "a=msid:noappdata" CRLF
 "a=bundle-only" CRLF;
 
 TEST_P(NewSdpTest, BasicAudioVideoSdpParse) {
   ParseSdp(kBasicAudioVideoOffer);
 }
 
+TEST_P(NewSdpTest, CheckRemoveFmtp) {
+  ParseSdp(kBasicAudioVideoOffer);
+  ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+  ASSERT_EQ(3U, mSdp->GetMediaSectionCount())
+    << "Wrong number of media sections";
+
+  SdpAttributeList& audioAttrList = mSdp->GetMediaSection(0).GetAttributeList();
+
+  ASSERT_TRUE(audioAttrList.HasAttribute(SdpAttribute::kFmtpAttribute));
+  ASSERT_EQ(2U, audioAttrList.GetFmtp().mFmtps.size());
+  ASSERT_TRUE(mSdp->GetMediaSection(0).FindFmtp("109"));
+  ASSERT_TRUE(mSdp->GetMediaSection(0).FindFmtp("101"));
+
+  mSdp->GetMediaSection(0).RemoveFmtp("101");
+
+  ASSERT_TRUE(audioAttrList.HasAttribute(SdpAttribute::kFmtpAttribute));
+  ASSERT_EQ(1U, audioAttrList.GetFmtp().mFmtps.size());
+  ASSERT_TRUE(mSdp->GetMediaSection(0).FindFmtp("109"));
+  ASSERT_FALSE(mSdp->GetMediaSection(0).FindFmtp("101"));
+
+  mSdp->GetMediaSection(0).RemoveFmtp("109");
+
+  ASSERT_TRUE(audioAttrList.HasAttribute(SdpAttribute::kFmtpAttribute));
+  ASSERT_EQ(0U, audioAttrList.GetFmtp().mFmtps.size());
+  ASSERT_FALSE(mSdp->GetMediaSection(0).FindFmtp("109"));
+  ASSERT_FALSE(mSdp->GetMediaSection(0).FindFmtp("101"));
+
+  // make sure we haven't disturbed the video fmtps
+  SdpAttributeList& videoAttrList = mSdp->GetMediaSection(1).GetAttributeList();
+  ASSERT_TRUE(videoAttrList.HasAttribute(SdpAttribute::kFmtpAttribute));
+  ASSERT_EQ(2U, videoAttrList.GetFmtp().mFmtps.size());
+  ASSERT_TRUE(mSdp->GetMediaSection(1).FindFmtp("120"));
+  ASSERT_TRUE(mSdp->GetMediaSection(1).FindFmtp("121"));
+}
+
 TEST_P(NewSdpTest, CheckIceUfrag) {
   ParseSdp(kBasicAudioVideoOffer);
   ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
   ASSERT_TRUE(mSdp->GetAttributeList().HasAttribute(
         SdpAttribute::kIceUfragAttribute));
   auto ice_ufrag = mSdp->GetAttributeList().GetIceUfrag();
   ASSERT_EQ("4a799b2e", ice_ufrag) << "Wrong ice-ufrag value";
 
@@ -1527,17 +1578,17 @@ TEST_P(NewSdpTest, CheckRtpmap) {
               SdpRtpmapAttributeList::kPCMA,
               "PCMA",
               8000,
               1,
               audiosec.GetFormats()[3],
               rtpmap);
 
   CheckRtpmap("101",
-              SdpRtpmapAttributeList::kOtherCodec,
+              SdpRtpmapAttributeList::kTelephoneEvent,
               "telephone-event",
               8000,
               1,
               audiosec.GetFormats()[4],
               rtpmap);
 
   const SdpMediaSection& videosec = mSdp->GetMediaSection(1);
   const SdpRtpmapAttributeList videoRtpmap =
@@ -1573,16 +1624,242 @@ TEST_P(NewSdpTest, CheckRtpmap) {
               SdpRtpmapAttributeList::kUlpfec,
               "ulpfec",
               90000,
               0,
               videosec.GetFormats()[3],
               videoRtpmap);
 }
 
+static const std::string kAudioWithTelephoneEvent =
+  "v=0" CRLF
+  "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF
+  "s=SIP Call" CRLF
+  "c=IN IP4 198.51.100.7" CRLF
+  "t=0 0" CRLF
+  "m=audio 9 RTP/SAVPF 109 9 0 8 101" CRLF
+  "c=IN IP4 0.0.0.0" CRLF
+  "a=mid:first" CRLF
+  "a=rtpmap:109 opus/48000/2" CRLF
+  "a=fmtp:109 maxplaybackrate=32000;stereo=1" CRLF
+  "a=ptime:20" CRLF
+  "a=maxptime:20" CRLF
+  "a=rtpmap:9 G722/8000" CRLF
+  "a=rtpmap:0 PCMU/8000" CRLF
+  "a=rtpmap:8 PCMA/8000" CRLF
+  "a=rtpmap:101 telephone-event/8000" CRLF;
+
+TEST_P(NewSdpTest, CheckTelephoneEventNoFmtp) {
+  ParseSdp(kAudioWithTelephoneEvent);
+  ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+  ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+    << "Wrong number of media sections";
+
+  ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+              SdpAttribute::kFmtpAttribute));
+  auto audio_format_params =
+      mSdp->GetMediaSection(0).GetAttributeList().GetFmtp().mFmtps;
+  ASSERT_EQ(1U, audio_format_params.size());
+
+  // make sure we don't get a fmtp for codec 101
+  for (size_t i = 0; i < audio_format_params.size(); ++i) {
+    ASSERT_NE("101", audio_format_params[i].format);
+  }
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventWithDefaultEvents) {
+  ParseSdp(kAudioWithTelephoneEvent
+           + "a=fmtp:101 0-15" CRLF);
+  ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+  ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+    << "Wrong number of media sections";
+
+  CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventWithBadCharacter) {
+  ParseSdp(kAudioWithTelephoneEvent
+           + "a=fmtp:101 0-5." CRLF);
+  ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+  ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+    << "Wrong number of media sections";
+
+  CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventIncludingCommas) {
+  ParseSdp(kAudioWithTelephoneEvent
+           + "a=fmtp:101 0-15,66,67" CRLF);
+  ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+  ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+    << "Wrong number of media sections";
+
+  CheckDtmfFmtp("0-15,66,67");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventComplexEvents) {
+  ParseSdp(kAudioWithTelephoneEvent
+           + "a=fmtp:101 0,1,2-4,5-15,66,67" CRLF);
+  ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+  ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+    << "Wrong number of media sections";
+
+  CheckDtmfFmtp("0,1,2-4,5-15,66,67");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventNoHyphen) {
+  ParseSdp(kAudioWithTelephoneEvent
+           + "a=fmtp:101 5,6,7" CRLF);
+  ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+  ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+    << "Wrong number of media sections";
+
+  CheckDtmfFmtp("5,6,7");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventOnlyZero) {
+  ParseSdp(kAudioWithTelephoneEvent
+           + "a=fmtp:101 0" CRLF);
+  ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+  ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+    << "Wrong number of media sections";
+
+  CheckDtmfFmtp("0");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventOnlyOne) {
+  ParseSdp(kAudioWithTelephoneEvent
+           + "a=fmtp:101 1" CRLF);
+  ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+  ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+    << "Wrong number of media sections";
+
+  CheckDtmfFmtp("1");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadThreeDigit) {
+  ParseSdp(kAudioWithTelephoneEvent
+           + "a=fmtp:101 123" CRLF);
+  ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+  ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+    << "Wrong number of media sections";
+
+  // check for the default dtmf tones
+  CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadThreeDigitWithHyphen) {
+  ParseSdp(kAudioWithTelephoneEvent
+           + "a=fmtp:101 0-123" CRLF);
+  ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+  ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+    << "Wrong number of media sections";
+
+  // check for the default dtmf tones
+  CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadLeadingHyphen) {
+  ParseSdp(kAudioWithTelephoneEvent
+           + "a=fmtp:101 -12" CRLF);
+  ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+  ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+    << "Wrong number of media sections";
+
+  // check for the default dtmf tones
+  CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadTrailingHyphen) {
+  ParseSdp(kAudioWithTelephoneEvent
+           + "a=fmtp:101 12-" CRLF, false);
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadTrailingHyphenInMiddle) {
+  ParseSdp(kAudioWithTelephoneEvent
+           + "a=fmtp:101 1,12-,4" CRLF, false);
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadLeadingComma) {
+  ParseSdp(kAudioWithTelephoneEvent
+           + "a=fmtp:101 ,2,3" CRLF);
+  ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+  ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+    << "Wrong number of media sections";
+
+  // check for the default dtmf tones
+  CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadMultipleLeadingComma) {
+  ParseSdp(kAudioWithTelephoneEvent
+           + "a=fmtp:101 ,,,2,3" CRLF);
+  ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+  ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+    << "Wrong number of media sections";
+
+  // check for the default dtmf tones
+  CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadConsecutiveCommas) {
+  ParseSdp(kAudioWithTelephoneEvent
+           + "a=fmtp:101 1,,,,,,,,3" CRLF);
+  ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+  ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+    << "Wrong number of media sections";
+
+  // check for the default dtmf tones
+  CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadTrailingComma) {
+  ParseSdp(kAudioWithTelephoneEvent
+           + "a=fmtp:101 1,2,3," CRLF);
+  ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+  ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+    << "Wrong number of media sections";
+
+  // check for the default dtmf tones
+  CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadTwoHyphens) {
+  ParseSdp(kAudioWithTelephoneEvent
+           + "a=fmtp:101 1-2-3" CRLF);
+  ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+  ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+    << "Wrong number of media sections";
+
+  // check for the default dtmf tones
+  CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadSixDigit) {
+  ParseSdp(kAudioWithTelephoneEvent
+           + "a=fmtp:101 112233" CRLF);
+  ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+  ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+    << "Wrong number of media sections";
+
+  // check for the default dtmf tones
+  CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadRangeReversed) {
+  ParseSdp(kAudioWithTelephoneEvent
+           + "a=fmtp:101 33-2" CRLF);
+  ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+  ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+    << "Wrong number of media sections";
+
+  // check for the default dtmf tones
+  CheckDtmfFmtp("0-15");
+}
+
 static const std::string kVideoWithRedAndUlpfecSdp =
   "v=0" CRLF
   "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF
   "s=SIP Call" CRLF
   "c=IN IP4 198.51.100.7" CRLF
   "t=0 0" CRLF
   "m=video 9 RTP/SAVPF 97 120 121 122 123" CRLF
   "c=IN IP6 ::1" CRLF
@@ -1683,16 +1960,17 @@ const std::string kH264AudioVideoOffer =
 "a=rtpmap:109 opus/48000/2" CRLF
 "a=ptime:20" CRLF
 "a=maxptime:20" CRLF
 "a=rtpmap:9 G722/8000" CRLF
 "a=rtpmap:0 PCMU/8000" CRLF
 "a=rtpmap:8 PCMA/8000" CRLF
 "a=rtpmap:101 telephone-event/8000" CRLF
 "a=fmtp:109 maxplaybackrate=32000;stereo=1;useinbandfec=1" CRLF
+"a=fmtp:101 0-15,66,32-34,67" CRLF
 "a=ice-ufrag:00000000" CRLF
 "a=ice-pwd:0000000000000000000000000000000" CRLF
 "a=sendonly" CRLF
 "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level" CRLF
 "a=setup:actpass" CRLF
 "a=rtcp-mux" CRLF
 "a=msid:stream track" CRLF
 "a=candidate:0 1 UDP 2130379007 10.0.0.36 62453 typ host" CRLF
@@ -1736,25 +2014,32 @@ TEST_P(NewSdpTest, CheckFormatParameters
   ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
   ASSERT_EQ(3U, mSdp->GetMediaSectionCount())
     << "Wrong number of media sections";
 
   ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
       SdpAttribute::kFmtpAttribute));
   auto audio_format_params =
       mSdp->GetMediaSection(0).GetAttributeList().GetFmtp().mFmtps;
-  ASSERT_EQ(1U, audio_format_params.size());
+  ASSERT_EQ(2U, audio_format_params.size());
   ASSERT_EQ("109", audio_format_params[0].format);
   ASSERT_TRUE(!!audio_format_params[0].parameters);
   const SdpFmtpAttributeList::OpusParameters* opus_parameters =
     static_cast<SdpFmtpAttributeList::OpusParameters*>(
         audio_format_params[0].parameters.get());
   ASSERT_EQ(32000U, opus_parameters->maxplaybackrate);
   ASSERT_EQ(1U, opus_parameters->stereo);
   ASSERT_EQ(1U, opus_parameters->useInBandFec);
+  ASSERT_EQ("101", audio_format_params[1].format);
+  ASSERT_TRUE(!!audio_format_params[1].parameters);
+  const SdpFmtpAttributeList::TelephoneEventParameters* te_parameters =
+    static_cast<SdpFmtpAttributeList::TelephoneEventParameters*>(
+        audio_format_params[1].parameters.get());
+  ASSERT_NE(0U, te_parameters->dtmfTones.size());
+  ASSERT_EQ("0-15,66,32-34,67", te_parameters->dtmfTones);
 
   ASSERT_TRUE(mSdp->GetMediaSection(1).GetAttributeList().HasAttribute(
       SdpAttribute::kFmtpAttribute));
   auto video_format_params =
       mSdp->GetMediaSection(1).GetAttributeList().GetFmtp().mFmtps;
   ASSERT_EQ(3U, video_format_params.size());
   ASSERT_EQ("97", video_format_params[0].format);
   ASSERT_TRUE(!!video_format_params[0].parameters);