Bug 1291714 - sdp changes to support DTMF signaling. r=bwc draft
authorMichael Froman <mfroman@mozilla.com>
Tue, 13 Sep 2016 22:15:55 -0500
changeset 416263 e1cf97c0a3ddc238d3df309787840904e7daf115
parent 413053 f5d043ce6d36a3c461cbd829d4a4a38394b7c436
child 416264 dc233195629554f3148bd3fdd88e30bba440e9bb
child 416265 5b70150233ce0539476635e1f0feecfa6aed4593
child 416678 ef20502614102eb378e66653b8c6b88ddcba6e66
push id30079
push userbmo:mfroman@nostrum.com
push dateWed, 21 Sep 2016 19:13:40 +0000
reviewersbwc
bugs1291714
milestone51.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/SipccSdpAttributeList.cpp
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
modules/libpref/init/all.js
--- 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::kAvt) {
+      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
@@ -2135,16 +2135,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* vp9 = new JsepVideoCodecDescription(
       "121",
       "VP9",
       90000
       );
   // Defaults for mandatory params
--- a/media/webrtc/signaling/src/jsep/JsepTrack.cpp
+++ b/media/webrtc/signaling/src/jsep/JsepTrack.cpp
@@ -339,26 +339,30 @@ 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;
   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 +385,52 @@ 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) {
+      if (codec->mType == SdpMediaSection::kAudio
+          && codec->mName != "telephone-event") {
+        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 = 1 + (dtmf ? 1 : 0);
     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
@@ -887,17 +887,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
@@ -961,27 +962,31 @@ 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);
+
+      branch->GetBoolPref("media.navigator.audio.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);
 
@@ -1044,16 +1049,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::kAvt:
       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,
+    kAvt, // telephone-event
     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::kAvt:
+      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::kAvt),
+      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/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_AVT:
+      return SdpRtpmapAttributeList::kAvt;
     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_AVT: { // 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/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_AVT       "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_AVT) == 0) {
+          return (RTP_AVT); // 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
@@ -1796,20 +1796,25 @@ 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 (strspn(tmp, "0123456789,-") == strlen(tmp)
+                   && ('0' <= tmp[0] && tmp[0] <= '9')
+                   && (('0' <= tmp[1] && tmp[1] <= '9')
+                       || tmp[1] == '-' || tmp[1] == ',')
+                  ) {
           // 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));
+        } 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
@@ -2714,17 +2714,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(
@@ -2841,16 +2841,98 @@ TEST_F(JsepSessionTest, ValidateOfferedC
   ASSERT_EQ(5U, parsed_red_params.encodings.size());
   ASSERT_EQ(121, parsed_red_params.encodings[0]);
   ASSERT_EQ(120, 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::kAvt, 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,50 @@ class JsepTrackTest : public ::testing::
       CheckEncodingCount(expected, mSendOff, mRecvAns);
     }
 
     void CheckAnsEncodingCount(size_t expected) const
     {
       CheckEncodingCount(expected, mSendAns, mRecvOff);
     }
 
-    const JsepVideoCodecDescription*
-    GetVideoCodec(const JsepTrack& track,
-                  size_t expectedSize = 1,
-                  size_t codecIndex = 0) const
+    const JsepCodecDescription* GetCodec(const JsepTrack& track,
+                                         SdpMediaSection::MediaType type,
+                                         size_t expectedSize,
+                                         size_t codecIndex) 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) {
+      if (codecs.size() != expectedSize || codecIndex >= expectedSize ||
+          codecs[codecIndex]->mType != type) {
         return nullptr;
       }
-      return static_cast<const JsepVideoCodecDescription*>(codecs[0]);
+      return codecs[codecIndex];
+    }
+
+    const JsepVideoCodecDescription*
+    GetVideoCodec(const JsepTrack& track,
+                  size_t expectedSize = 1,
+                  size_t codecIndex = 0) const
+    {
+      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);
     }
@@ -364,16 +389,33 @@ class JsepTrackTest : public ::testing::
       if (mSendOff && mRecvAns) {
         SanityCheckTracks(*mSendOff, *mRecvAns);
       }
       if (mRecvOff && mSendAns) {
         SanityCheckTracks(*mRecvOff, *mSendAns);
       }
     }
 
+    void RemoveFmtp(SdpMediaSection& mediaSection, const char* pt)
+    {
+      UniquePtr<SdpFmtpAttributeList> fmtps(new SdpFmtpAttributeList);
+
+      SdpAttributeList& attrList = mediaSection.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());
+    }
+
   protected:
     RefPtr<JsepTrack> mSendOff;
     RefPtr<JsepTrack> mRecvOff;
     RefPtr<JsepTrack> mSendAns;
     RefPtr<JsepTrack> mRecvAns;
     PtrVector<JsepCodecDescription> mOffCodecs;
     PtrVector<JsepCodecDescription> mAnsCodecs;
     UniquePtr<Sdp> mOffer;
@@ -396,16 +438,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();
+  RemoveFmtp(GetOffer(), "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();
+  RemoveFmtp(GetAnswer(), "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();
+  RemoveFmtp(GetOffer(), "101");
+
+  CreateAnswer();
+  RemoveFmtp(GetAnswer(), "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
@@ -1206,17 +1206,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
@@ -1527,17 +1527,17 @@ TEST_P(NewSdpTest, CheckRtpmap) {
               SdpRtpmapAttributeList::kPCMA,
               "PCMA",
               8000,
               1,
               audiosec.GetFormats()[3],
               rtpmap);
 
   CheckRtpmap("101",
-              SdpRtpmapAttributeList::kOtherCodec,
+              SdpRtpmapAttributeList::kAvt,
               "telephone-event",
               8000,
               1,
               audiosec.GetFormats()[4],
               rtpmap);
 
   const SdpMediaSection& videosec = mSdp->GetMediaSection(1);
   const SdpRtpmapAttributeList videoRtpmap =
@@ -1573,16 +1573,163 @@ 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";
+
+  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("0-15", te_parameters->dtmfTones);
+}
+
+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";
+
+  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());
+  // check for the default dtmf tones
+  ASSERT_EQ("0-15", te_parameters->dtmfTones);
+}
+
+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";
+
+  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("0-15,66,67", te_parameters->dtmfTones);
+}
+
+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";
+
+  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("0,1,2-4,5-15,66,67", te_parameters->dtmfTones);
+}
+
+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";
+
+  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("5,6,7", te_parameters->dtmfTones);
+}
+
 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 +1830,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 +1884,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);
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -408,16 +408,17 @@ pref("media.navigator.load_adapt.measure
 pref("media.navigator.load_adapt.avg_seconds",3);
 pref("media.navigator.load_adapt.high_load","0.90");
 pref("media.navigator.load_adapt.low_load","0.40");
 pref("media.navigator.video.default_fps",30);
 pref("media.navigator.video.default_minfps",10);
 pref("media.navigator.video.use_remb", true);
 pref("media.navigator.video.use_tmmbr", false);
 pref("media.navigator.audio.use_fec", true);
+pref("media.navigator.audio.dtmf_enabled", false);
 pref("media.navigator.video.red_ulpfec_enabled", false);
 
 pref("media.webrtc.debug.trace_mask", 0);
 pref("media.webrtc.debug.multi_log", false);
 pref("media.webrtc.debug.aec_log_dir", "");
 pref("media.webrtc.debug.log_file", "");
 pref("media.webrtc.debug.aec_dump_max_size", 4194304); // 4MB