--- 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);