--- a/media/webrtc/signaling/src/jsep/JsepCodecDescription.h
+++ b/media/webrtc/signaling/src/jsep/JsepCodecDescription.h
@@ -45,16 +45,17 @@ class JsepCodecDescription {
GetPtAsInt(uint16_t* ptOutparam) const
{
return SdpHelper::GetPtAsInt(mDefaultPt, ptOutparam);
}
virtual bool
Matches(const std::string& fmt, const SdpMediaSection& remoteMsection) const
{
+ // note: fmt here is remote fmt (to go with remoteMsection)
if (mType != remoteMsection.GetMediaType()) {
return false;
}
const SdpRtpmapAttributeList::Rtpmap* entry(remoteMsection.FindRtpmap(fmt));
if (entry) {
if (!nsCRT::strcasecmp(mName.c_str(), entry->name.c_str())
@@ -209,16 +210,17 @@ class JsepVideoCodecDescription : public
JsepVideoCodecDescription(const std::string& defaultPt,
const std::string& name,
uint32_t clock,
bool enabled = true)
: JsepCodecDescription(mozilla::SdpMediaSection::kVideo, defaultPt, name,
clock, 0, enabled),
mTmmbrEnabled(false),
mRembEnabled(false),
+ mFECEnabled(false),
mPacketizationMode(0)
{
// Add supported rtcp-fb types
mNackFbTypes.push_back("");
mNackFbTypes.push_back(SdpRtcpFbAttributeList::pli);
mCcmFbTypes.push_back(SdpRtcpFbAttributeList::fir);
}
@@ -237,16 +239,26 @@ class JsepVideoCodecDescription : public
// EnableRemb can be called multiple times due to multiple calls to
// PeerConnectionImpl::ConfigureJsepSessionCodecs
if (!mRembEnabled) {
mRembEnabled = true;
mOtherFbTypes.push_back({ "", SdpRtcpFbAttributeList::kRemb, "", ""});
}
}
+ virtual void
+ EnableFec() {
+ // Enabling FEC for video works a little differently than enabling
+ // REMB or TMMBR. Support for FEC is indicated by the presence of
+ // particular codes (red and ulpfec) instead of using rtcpfb
+ // attributes on a given codec. There is no rtcpfb to push for FEC
+ // as can be seen above when REMB or TMMBR are enabled.
+ mFECEnabled = true;
+ }
+
void
AddParametersToMSection(SdpMediaSection& msection) const override
{
AddFmtpsToMSection(msection);
AddRtcpFbsToMSection(msection);
}
void
@@ -277,16 +289,21 @@ class JsepVideoCodecDescription : public
}
// Parameters that apply to both the send and recv directions
h264Params.packetization_mode = mPacketizationMode;
// Hard-coded, may need to change someday?
h264Params.level_asymmetry_allowed = true;
msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, h264Params));
+ } else if (mName == "red") {
+ SdpFmtpAttributeList::RedParameters redParams(
+ GetRedParameters(mDefaultPt, msection));
+ redParams.encodings = mRedundantEncodings;
+ msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, redParams));
} else if (mName == "VP8" || mName == "VP9") {
if (mDirection == sdp::kRecv) {
// VP8 and VP9 share the same SDP parameters thus far
SdpFmtpAttributeList::VP8Parameters vp8Params(
GetVP8Parameters(mDefaultPt, msection));
vp8Params.max_fs = mConstraints.maxFs;
vp8Params.max_fr = mConstraints.maxFps;
@@ -333,16 +350,31 @@ class JsepVideoCodecDescription : public
if (params && params->codec_type == SdpRtpmapAttributeList::kH264) {
result =
static_cast<const SdpFmtpAttributeList::H264Parameters&>(*params);
}
return result;
}
+ SdpFmtpAttributeList::RedParameters
+ GetRedParameters(const std::string& pt,
+ const SdpMediaSection& msection) const
+ {
+ SdpFmtpAttributeList::RedParameters result;
+ auto* params = msection.FindFmtp(pt);
+
+ if (params && params->codec_type == SdpRtpmapAttributeList::kRed) {
+ result =
+ static_cast<const SdpFmtpAttributeList::RedParameters&>(*params);
+ }
+
+ return result;
+ }
+
SdpFmtpAttributeList::VP8Parameters
GetVP8Parameters(const std::string& pt,
const SdpMediaSection& msection) const
{
SdpRtpmapAttributeList::CodecType expectedType(
mName == "VP8" ?
SdpRtpmapAttributeList::kVP8 :
SdpRtpmapAttributeList::kVP9);
@@ -422,17 +454,20 @@ class JsepVideoCodecDescription : public
// Only do this if we didn't symmetrically negotiate above
if (h264Params.level_asymmetry_allowed) {
SetSaneH264Level(GetSaneH264Level(h264Params.profile_level_id),
&mProfileLevelId);
}
} else {
// TODO(bug 1143709): max-recv-level support
}
-
+ } else if (mName == "red") {
+ SdpFmtpAttributeList::RedParameters redParams(
+ GetRedParameters(mDefaultPt, remoteMsection));
+ mRedundantEncodings = redParams.encodings;
} else if (mName == "VP8" || mName == "VP9") {
if (mDirection == sdp::kSend) {
SdpFmtpAttributeList::VP8Parameters vp8Params(
GetVP8Parameters(mDefaultPt, remoteMsection));
mConstraints.maxFs = vp8Params.max_fs;
mConstraints.maxFps = vp8Params.max_fr;
}
@@ -644,24 +679,44 @@ class JsepVideoCodecDescription : public
for (const auto& fb : mOtherFbTypes) {
if (fb.type == SdpRtcpFbAttributeList::kRemb) {
return true;
}
}
return false;
}
+ virtual void
+ UpdateRedundantEncodings(std::vector<JsepCodecDescription*> codecs)
+ {
+ for (const auto codec : codecs) {
+ if (codec->mType == SdpMediaSection::kVideo &&
+ codec->mEnabled &&
+ codec->mName != "red") {
+ uint8_t pt = (uint8_t)strtoul(codec->mDefaultPt.c_str(), nullptr, 10);
+ // returns 0 if failed to convert, and since zero could
+ // be valid, check the defaultPt for 0
+ if (pt == 0 && codec->mDefaultPt != "0") {
+ continue;
+ }
+ mRedundantEncodings.push_back(pt);
+ }
+ }
+ }
+
JSEP_CODEC_CLONE(JsepVideoCodecDescription)
std::vector<std::string> mAckFbTypes;
std::vector<std::string> mNackFbTypes;
std::vector<std::string> mCcmFbTypes;
std::vector<SdpRtcpFbAttributeList::Feedback> mOtherFbTypes;
bool mTmmbrEnabled;
bool mRembEnabled;
+ bool mFECEnabled;
+ std::vector<uint8_t> mRedundantEncodings;
// H264-specific stuff
uint32_t mProfileLevelId;
uint32_t mPacketizationMode;
std::string mSpropParameterSets;
};
class JsepApplicationCodecDescription : public JsepCodecDescription {
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
@@ -2177,21 +2177,39 @@ JsepSessionImpl::SetupDefaultCodecs()
"H264",
90000
);
h264_0->mPacketizationMode = 0;
// Defaults for mandatory params
h264_0->mProfileLevelId = 0x42E00D;
mSupportedCodecs.values.push_back(h264_0);
+ JsepVideoCodecDescription* red = new JsepVideoCodecDescription(
+ "122", // payload type
+ "red", // codec name
+ 90000 // clock rate (match other video codecs)
+ );
+ mSupportedCodecs.values.push_back(red);
+
+ JsepVideoCodecDescription* ulpfec = new JsepVideoCodecDescription(
+ "123", // payload type
+ "ulpfec", // codec name
+ 90000 // clock rate (match other video codecs)
+ );
+ mSupportedCodecs.values.push_back(ulpfec);
+
mSupportedCodecs.values.push_back(new JsepApplicationCodecDescription(
"5000",
"webrtc-datachannel",
WEBRTC_DATACHANNEL_STREAMS_DEFAULT
));
+
+ // Update the redundant encodings for the RED codec with the supported
+ // codecs. Note: only uses the video codecs.
+ red->UpdateRedundantEncodings(mSupportedCodecs.values);
}
void
JsepSessionImpl::SetupDefaultRtpExtensions()
{
AddAudioRtpExtension("urn:ietf:params:rtp-hdrext:ssrc-audio-level");
}
--- a/media/webrtc/signaling/src/jsep/JsepTrack.cpp
+++ b/media/webrtc/signaling/src/jsep/JsepTrack.cpp
@@ -339,23 +339,68 @@ JsepTrack::NegotiateCodecs(
if (formatChanges) {
(*formatChanges)[originalFormat] = codec->mDefaultPt;
}
break;
}
}
}
+ // Find the (potential) red codec and ulpfec codec
+ JsepVideoCodecDescription* red = nullptr;
+ JsepVideoCodecDescription* ulpfec = nullptr;
+ for (auto codec : *codecs) {
+ if (codec->mName == "red") {
+ red = static_cast<JsepVideoCodecDescription*>(codec);
+ break;
+ }
+ if (codec->mName == "ulpfec") {
+ ulpfec = static_cast<JsepVideoCodecDescription*>(codec);
+ break;
+ }
+ }
+ // 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);
+ for (auto redundantPt : unnegotiatedEncodings) {
+ std::string pt = std::to_string(redundantPt);
+ for (auto codec : *codecs) {
+ if (pt == codec->mDefaultPt) {
+ red->mRedundantEncodings.push_back(redundantPt);
+ break;
+ }
+ }
+ }
+ }
+ // Video FEC is indicated by the existence of the red and ulpfec
+ // codecs and not an attribute on the particular video codec (like in
+ // a rtcpfb attr). If we see both red and ulpfec codecs, we enable FEC
+ // on all the other codecs.
+ if (red && ulpfec) {
+ for (auto codec : *codecs) {
+ if (codec->mName != "red" && codec->mName != "ulpfec") {
+ JsepVideoCodecDescription* videoCodec =
+ static_cast<JsepVideoCodecDescription*>(codec);
+ videoCodec->EnableFec();
+ }
+ }
+ }
+
// Make sure strongly preferred codecs are up front, overriding the remote
// side's preference.
std::stable_sort(codecs->begin(), codecs->end(), CompareCodec);
// TODO(bug 814227): Remove this once we're ready to put multiple codecs in an
- // answer
- if (!codecs->empty()) {
+ // 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.
+ if (!codecs->empty() && !red) {
for (size_t i = 1; i < codecs->size(); ++i) {
delete (*codecs)[i];
(*codecs)[i] = nullptr;
}
codecs->resize(1);
}
}
--- a/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
+++ b/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
@@ -144,16 +144,17 @@ JsepCodecDescToCodecConfig(const JsepCod
VideoCodecConfig* configRaw;
configRaw = new VideoCodecConfig(
pt, desc.mName, desc.mConstraints, h264Config.get());
configRaw->mAckFbTypes = desc.mAckFbTypes;
configRaw->mNackFbTypes = desc.mNackFbTypes;
configRaw->mCcmFbTypes = desc.mCcmFbTypes;
configRaw->mRembFbSet = desc.RtcpFbRembIsSet();
+ configRaw->mFECFbSet = desc.mFECEnabled;
*aConfig = configRaw;
return NS_OK;
}
static nsresult
NegotiatedDetailsToVideoCodecConfigs(const JsepTrackNegotiatedDetails& aDetails,
PtrVector<VideoCodecConfig>* aConfigs)
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -881,17 +881,18 @@ class ConfigureCodec {
mVP9Enabled(false),
mH264Level(13), // minimum suggested for WebRTC spec
mH264MaxBr(0), // Unlimited
mH264MaxMbps(0), // Unlimited
mVP8MaxFs(0),
mVP8MaxFr(0),
mUseTmmbr(false),
mUseRemb(false),
- mUseAudioFec(false)
+ mUseAudioFec(false),
+ mRedUlpfecEnabled(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
@@ -952,16 +953,19 @@ class ConfigureCodec {
// TMMBR is enabled from a pref in about:config
branch->GetBoolPref("media.navigator.video.use_tmmbr", &mUseTmmbr);
// 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);
}
void operator()(JsepCodecDescription* codec) const
{
switch (codec->mType) {
case SdpMediaSection::kAudio:
{
JsepAudioCodecDescription& audioCodec =
@@ -992,16 +996,20 @@ class ConfigureCodec {
// We're assuming packetization mode 0 is unsupported by
// hardware.
videoCodec.mEnabled = false;
}
if (mHardwareH264Supported) {
videoCodec.mStronglyPreferred = true;
}
+ } else if (videoCodec.mName == "red") {
+ videoCodec.mEnabled = mRedUlpfecEnabled;
+ } else if (videoCodec.mName == "ulpfec") {
+ videoCodec.mEnabled = mRedUlpfecEnabled;
} else if (videoCodec.mName == "VP8" || videoCodec.mName == "VP9") {
if (videoCodec.mName == "VP9" && !mVP9Enabled) {
videoCodec.mEnabled = false;
break;
}
videoCodec.mConstraints.maxFs = mVP8MaxFs;
videoCodec.mConstraints.maxFps = mVP8MaxFr;
}
@@ -1030,16 +1038,51 @@ class ConfigureCodec {
int32_t mH264Level;
int32_t mH264MaxBr;
int32_t mH264MaxMbps;
int32_t mVP8MaxFs;
int32_t mVP8MaxFr;
bool mUseTmmbr;
bool mUseRemb;
bool mUseAudioFec;
+ bool mRedUlpfecEnabled;
+};
+
+class ConfigureRedCodec {
+ public:
+ explicit ConfigureRedCodec(nsCOMPtr<nsIPrefBranch>& branch,
+ std::vector<uint8_t>* redundantEncodings) :
+ mRedundantEncodings(redundantEncodings)
+ {
+ // if we wanted to override or modify which encodings are considered
+ // for redundant encodings, we'd probably want to handle it here by
+ // checking prefs modifying the operator() code below
+ }
+
+ void operator()(JsepCodecDescription* codec) const
+ {
+ if (codec->mType == SdpMediaSection::kVideo &&
+ codec->mEnabled == false) {
+ uint8_t pt = (uint8_t)strtoul(codec->mDefaultPt.c_str(), nullptr, 10);
+ // don't search for the codec payload type unless we have a valid
+ // conversion (non-zero)
+ if (pt != 0) {
+ std::vector<uint8_t>::iterator it =
+ std::find(mRedundantEncodings->begin(),
+ mRedundantEncodings->end(),
+ pt);
+ if (it != mRedundantEncodings->end()) {
+ mRedundantEncodings->erase(it);
+ }
+ }
+ }
+ }
+
+ private:
+ std::vector<uint8_t>* mRedundantEncodings;
};
nsresult
PeerConnectionImpl::ConfigureJsepSessionCodecs() {
nsresult res;
nsCOMPtr<nsIPrefService> prefs =
do_GetService("@mozilla.org/preferences-service;1", &res);
@@ -1054,16 +1097,33 @@ PeerConnectionImpl::ConfigureJsepSession
if (!branch) {
CSFLogError(logTag, "%s: Couldn't get prefs branch", __FUNCTION__);
return NS_ERROR_FAILURE;
}
ConfigureCodec configurer(branch);
mJsepSession->ForEachCodec(configurer);
+ // first find the red codec description
+ std::vector<JsepCodecDescription*>& codecs = mJsepSession->Codecs();
+ JsepVideoCodecDescription* redCodec = nullptr;
+ for (auto codec : codecs) {
+ // we only really care about finding the RED codec if it is
+ // enabled
+ if (codec->mName == "red" && codec->mEnabled) {
+ redCodec = static_cast<JsepVideoCodecDescription*>(codec);
+ break;
+ }
+ }
+ // if red codec was found, configure it for the other enabled codecs
+ if (redCodec) {
+ ConfigureRedCodec configureRed(branch, &(redCodec->mRedundantEncodings));
+ mJsepSession->ForEachCodec(configureRed);
+ }
+
// We use this to sort the list of codecs once everything is configured
CompareCodecPriority comparator;
// Sort by priority
int32_t preferredCodec = 0;
branch->GetIntPref("media.navigator.video.preferred_codec",
&preferredCodec);
--- a/media/webrtc/signaling/src/sdp/SdpAttribute.cpp
+++ b/media/webrtc/signaling/src/sdp/SdpAttribute.cpp
@@ -1097,16 +1097,18 @@ ShouldSerializeChannels(SdpRtpmapAttribu
return true;
case SdpRtpmapAttributeList::kPCMU:
case SdpRtpmapAttributeList::kPCMA:
case SdpRtpmapAttributeList::kVP8:
case SdpRtpmapAttributeList::kVP9:
case SdpRtpmapAttributeList::kiLBC:
case SdpRtpmapAttributeList::kiSAC:
case SdpRtpmapAttributeList::kH264:
+ case SdpRtpmapAttributeList::kRed:
+ case SdpRtpmapAttributeList::kUlpfec:
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
@@ -1018,16 +1018,18 @@ public:
kG722,
kPCMU,
kPCMA,
kVP8,
kVP9,
kiLBC,
kiSAC,
kH264,
+ kRed,
+ kUlpfec,
kOtherCodec
};
struct Rtpmap {
std::string pt;
CodecType codec;
std::string name;
uint32_t clock;
@@ -1097,16 +1099,22 @@ inline std::ostream& operator<<(std::ost
os << "iLBC";
break;
case SdpRtpmapAttributeList::kiSAC:
os << "iSAC";
break;
case SdpRtpmapAttributeList::kH264:
os << "H264";
break;
+ case SdpRtpmapAttributeList::kRed:
+ os << "red";
+ break;
+ case SdpRtpmapAttributeList::kUlpfec:
+ os << "ulpfec";
+ break;
default:
MOZ_ASSERT(false);
os << "?";
}
return os;
}
///////////////////////////////////////////////////////////////////////////
@@ -1130,16 +1138,42 @@ public:
virtual ~Parameters() {}
virtual Parameters* Clone() const = 0;
virtual void Serialize(std::ostream& os) const = 0;
SdpRtpmapAttributeList::CodecType codec_type;
};
+ class RedParameters : public Parameters
+ {
+ public:
+ RedParameters()
+ : Parameters(SdpRtpmapAttributeList::kRed)
+ {
+ }
+
+ virtual Parameters*
+ Clone() const override
+ {
+ return new RedParameters(*this);
+ }
+
+ virtual void
+ Serialize(std::ostream& os) const override
+ {
+ for(size_t i = 0; i < encodings.size(); ++i) {
+ os << (i != 0 ? "/" : "")
+ << std::to_string(encodings[i]);
+ }
+ }
+
+ std::vector<uint8_t> encodings;
+ };
+
class H264Parameters : public Parameters
{
public:
static const uint32_t kDefaultProfileLevelId = 0x420010;
H264Parameters()
: Parameters(SdpRtpmapAttributeList::kH264),
packetization_mode(0),
--- a/media/webrtc/signaling/src/sdp/SipccSdpAttributeList.cpp
+++ b/media/webrtc/signaling/src/sdp/SipccSdpAttributeList.cpp
@@ -354,16 +354,20 @@ SipccSdpAttributeList::GetCodecType(rtp_
case RTP_H264_P1:
return SdpRtpmapAttributeList::kH264;
case RTP_OPUS:
return SdpRtpmapAttributeList::kOpus;
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_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:
@@ -716,16 +720,27 @@ SipccSdpAttributeList::LoadFmtp(sdp_t* s
new SdpFmtpAttributeList::VP8Parameters(
SdpRtpmapAttributeList::kVP8));
vp8Parameters->max_fs = fmtp->max_fs;
vp8Parameters->max_fr = fmtp->max_fr;
parameters.reset(vp8Parameters);
} break;
+ case RTP_RED: {
+ SdpFmtpAttributeList::RedParameters* redParameters(
+ new SdpFmtpAttributeList::RedParameters);
+ for (int i = 0;
+ i < SDP_FMTP_MAX_REDUNDANT_ENCODINGS && fmtp->redundant_encodings[i];
+ ++i) {
+ redParameters->encodings.push_back(fmtp->redundant_encodings[i]);
+ }
+
+ parameters.reset(redParameters);
+ } break;
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;
--- a/media/webrtc/signaling/src/sdp/sipcc/ccsdp.h
+++ b/media/webrtc/signaling/src/sdp/sipcc/ccsdp.h
@@ -35,16 +35,18 @@ typedef enum rtp_ptype_
RTP_H264_P1 = 126,
RTP_AVT = 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,
RTP_I420 = 124,
RTP_ISAC = 124
} rtp_ptype;
typedef struct {
const char *name;
int value;
} ccsdp_key_table_entry_t;
--- a/media/webrtc/signaling/src/sdp/sipcc/sdp.h
+++ b/media/webrtc/signaling/src/sdp/sipcc/sdp.h
@@ -44,16 +44,19 @@
#define SDP_SRTP_MAX_MKI_SIZE_BYTES 4
/* Max number of characters for Lifetime */
#define SDP_SRTP_MAX_LIFETIME_BYTES 16
#define SDP_SDESCRIPTIONS_KEY_SIZE_UNKNOWN 0
#define SDP_SRTP_CRYPTO_SELECTION_FLAGS_UNKNOWN 0
+/* Max number of fmtp redundant encodings */
+#define SDP_FMTP_MAX_REDUNDANT_ENCODINGS 128
+
/*
* SRTP_CONTEXT_SET_*
* Set a SRTP Context field flag
*/
#define SDP_SRTP_ENCRYPT_MASK 0x00000001
#define SDP_SRTP_AUTHENTICATE_MASK 0x00000002
#define SDP_SRTCP_ENCRYPT_MASK 0x00000004
#define SDP_SRTCP_SSRC_MASK 0x20000000
@@ -705,16 +708,19 @@ typedef struct sdp_fmtp {
tinybool annex_i;
tinybool annex_j;
tinybool annex_t;
/* 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];
+
/* 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
@@ -24,16 +24,18 @@ static const char* logTag = "sdp_access"
#define SIPSDP_ATTR_ENCNAME_ILBC "iLBC"
#define SIPSDP_ATTR_ENCNAME_H263v2 "H263-1998"
#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"
/* 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.
@@ -1372,16 +1374,22 @@ rtp_ptype sdp_get_known_payload_type(sdp
}
}
if (cpr_strcasecmp(encname, SIPSDP_ATTR_ENCNAME_VP8) == 0) {
return (RTP_VP8);
}
if (cpr_strcasecmp(encname, SIPSDP_ATTR_ENCNAME_VP9) == 0) {
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);
+ }
}
}
}
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
@@ -1504,33 +1504,33 @@ sdp_result_e sdp_parse_attr_fmtp (sdp_t
sdp_attr_fmtp_no_value(sdp_p, "annex_p");
SDP_FREE(temp_ptr);
return SDP_INVALID_PARAMETER;
}
}
fmtp_p->annex_p_val_picture_resize = 0;
fmtp_p->annex_p_val_warp = 0;
tok = tmp;
- tok++; temp=PL_strtok_r(tok, ",", &strtok_state);
+ tok++; temp = PL_strtok_r(tok, ",", &strtok_state);
if (temp) {
iter=1;
while (temp != NULL) {
errno = 0;
strtoul_result = strtoul(temp, &strtoul_end, 10);
if (errno || temp == strtoul_end || strtoul_result > USHRT_MAX) {
break;
}
if (iter == 1)
fmtp_p->annex_p_val_picture_resize = (uint16_t) strtoul_result;
else if (iter == 2)
fmtp_p->annex_p_val_warp = (uint16_t) strtoul_result;
- temp=PL_strtok_r(NULL, ",", &strtok_state);
+ temp = PL_strtok_r(NULL, ",", &strtok_state);
iter++;
}
}
fmtp_p->fmtp_format = SDP_FMTP_CODEC_INFO;
codec_info_found = TRUE;
} else if (cpr_strncasecmp(tmp,sdp_fmtp_codec_param[42].name,
sdp_fmtp_codec_param[42].strlen) == 0) {
@@ -1775,16 +1775,37 @@ sdp_result_e sdp_parse_attr_fmtp (sdp_t
}
if (strchr(temp, 'T') !=NULL) {
attr_p->attr.fmtp.annex_t = TRUE;
}
temp=PL_strtok_r(NULL, ";", &strtok_state);
}
} /* if (temp) */
done = TRUE;
+ } else if (strchr(tmp, '/')) {
+ // XXX Note that because RFC 5109 so conveniently specified
+ // this fmtp with no param names, we hope that nothing else
+ // has a slash in the string because otherwise we won't know
+ // how to differentiate.
+ temp=PL_strtok_r(tmp, "/", &strtok_state);
+ if (temp) {
+ iter = 0;
+ while (temp != NULL) {
+ errno = 0;
+ strtoul_result = strtoul(temp, &strtoul_end, 10);
+
+ if (errno ||
+ 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 {
// XXX Note that DTMF fmtp will fall into here:
// a=fmtp:101 0-15 (or 0-15,NN,NN etc)
// 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",
--- a/media/webrtc/signaling/test/jsep_session_unittest.cpp
+++ b/media/webrtc/signaling/test/jsep_session_unittest.cpp
@@ -2737,45 +2737,53 @@ TEST_F(JsepSessionTest, ValidateOfferedC
ASSERT_TRUE(!!outputSdp);
ASSERT_EQ(2U, outputSdp->GetMediaSectionCount());
auto& video_section = outputSdp->GetMediaSection(1);
ASSERT_EQ(SdpMediaSection::kVideo, video_section.GetMediaType());
auto& video_attrs = video_section.GetAttributeList();
ASSERT_EQ(SdpDirectionAttribute::kSendrecv, video_attrs.GetDirection());
- ASSERT_EQ(4U, video_section.GetFormats().size());
+ ASSERT_EQ(6U, video_section.GetFormats().size());
ASSERT_EQ("121", video_section.GetFormats()[0]);
ASSERT_EQ("120", video_section.GetFormats()[1]);
ASSERT_EQ("126", video_section.GetFormats()[2]);
ASSERT_EQ("97", video_section.GetFormats()[3]);
+ ASSERT_EQ("122", video_section.GetFormats()[4]);
+ ASSERT_EQ("123", video_section.GetFormats()[5]);
// Validate rtpmap
ASSERT_TRUE(video_attrs.HasAttribute(SdpAttribute::kRtpmapAttribute));
auto& rtpmaps = video_attrs.GetRtpmap();
ASSERT_TRUE(rtpmaps.HasEntry("120"));
ASSERT_TRUE(rtpmaps.HasEntry("121"));
ASSERT_TRUE(rtpmaps.HasEntry("126"));
ASSERT_TRUE(rtpmaps.HasEntry("97"));
+ ASSERT_TRUE(rtpmaps.HasEntry("122"));
+ ASSERT_TRUE(rtpmaps.HasEntry("123"));
auto& vp8_entry = rtpmaps.GetEntry("120");
auto& vp9_entry = rtpmaps.GetEntry("121");
auto& h264_1_entry = rtpmaps.GetEntry("126");
auto& h264_0_entry = rtpmaps.GetEntry("97");
+ auto& red_0_entry = rtpmaps.GetEntry("122");
+ auto& ulpfec_0_entry = rtpmaps.GetEntry("123");
ASSERT_EQ("VP8", vp8_entry.name);
ASSERT_EQ("VP9", vp9_entry.name);
ASSERT_EQ("H264", h264_1_entry.name);
ASSERT_EQ("H264", h264_0_entry.name);
+ ASSERT_EQ("red", red_0_entry.name);
+ ASSERT_EQ("ulpfec", ulpfec_0_entry.name);
// Validate fmtps
ASSERT_TRUE(video_attrs.HasAttribute(SdpAttribute::kFmtpAttribute));
auto& fmtps = video_attrs.GetFmtp().mFmtps;
- ASSERT_EQ(4U, fmtps.size());
+ ASSERT_EQ(5U, fmtps.size());
// VP8
const SdpFmtpAttributeList::Parameters* vp8_params =
video_section.FindFmtp("120");
ASSERT_TRUE(vp8_params);
ASSERT_EQ(SdpRtpmapAttributeList::kVP8, vp8_params->codec_type);
auto& parsed_vp8_params =
@@ -2816,21 +2824,40 @@ TEST_F(JsepSessionTest, ValidateOfferedC
ASSERT_EQ(SdpRtpmapAttributeList::kH264, h264_0_params->codec_type);
auto& parsed_h264_0_params =
*static_cast<const SdpFmtpAttributeList::H264Parameters*>(h264_0_params);
ASSERT_EQ((uint32_t)0x42e00d, parsed_h264_0_params.profile_level_id);
ASSERT_TRUE(parsed_h264_0_params.level_asymmetry_allowed);
ASSERT_EQ(0U, parsed_h264_0_params.packetization_mode);
+
+ // red
+ const SdpFmtpAttributeList::Parameters* red_params =
+ video_section.FindFmtp("122");
+ ASSERT_TRUE(red_params);
+ ASSERT_EQ(SdpRtpmapAttributeList::kRed, red_params->codec_type);
+
+ auto& parsed_red_params =
+ *static_cast<const SdpFmtpAttributeList::RedParameters*>(red_params);
+ 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, 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();
++i) {
auto* codec = *i;
if (codec->mName == "H264") {
JsepVideoCodecDescription* h264 =
static_cast<JsepVideoCodecDescription*>(codec);
h264->mProfileLevelId = 0x42a00d;
// Switch up the pts
--- a/media/webrtc/signaling/test/jsep_track_unittest.cpp
+++ b/media/webrtc/signaling/test/jsep_track_unittest.cpp
@@ -27,43 +27,76 @@
namespace mozilla {
class JsepTrackTest : public ::testing::Test
{
public:
JsepTrackTest() {}
std::vector<JsepCodecDescription*>
- MakeCodecs() const
+ MakeCodecs(bool addFecCodecs = false, bool preferRed = 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));
+ JsepVideoCodecDescription* red = nullptr;
+ if (addFecCodecs && preferRed) {
+ red = new JsepVideoCodecDescription(
+ "122",
+ "red",
+ 90000
+ );
+ results.push_back(red);
+ }
+
JsepVideoCodecDescription* vp8 =
new JsepVideoCodecDescription("120", "VP8", 90000);
vp8->mConstraints.maxFs = 12288;
vp8->mConstraints.maxFps = 60;
results.push_back(vp8);
JsepVideoCodecDescription* h264 =
new JsepVideoCodecDescription("126", "H264", 90000);
h264->mPacketizationMode = 1;
h264->mProfileLevelId = 0x42E00D;
results.push_back(h264);
+ if (addFecCodecs) {
+ if (!preferRed) {
+ red = new JsepVideoCodecDescription(
+ "122",
+ "red",
+ 90000
+ );
+ results.push_back(red);
+ }
+ JsepVideoCodecDescription* ulpfec = new JsepVideoCodecDescription(
+ "123",
+ "ulpfec",
+ 90000
+ );
+ results.push_back(ulpfec);
+ }
+
results.push_back(
new JsepApplicationCodecDescription(
"5000",
"webrtc-datachannel",
16
));
+ // if we're doing something with red, it needs
+ // to update the redundant encodings list
+ if (red) {
+ red->UpdateRedundantEncodings(results);
+ }
+
return results;
}
void Init(SdpMediaSection::MediaType type) {
InitCodecs();
InitTracks(type);
InitSdp(type);
}
@@ -206,26 +239,28 @@ class JsepTrackTest : public ::testing::
}
void CheckAnsEncodingCount(size_t expected) const
{
CheckEncodingCount(expected, mSendAns, mRecvOff);
}
const JsepVideoCodecDescription*
- GetVideoCodec(const JsepTrack& track) const
+ 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() != 1U ||
- codecs[0]->mType != SdpMediaSection::kVideo) {
+ if (codecs.size() != expectedSize ||
+ codecs[codecIndex]->mType != SdpMediaSection::kVideo) {
return nullptr;
}
return static_cast<const JsepVideoCodecDescription*>(codecs[0]);
}
void CheckOtherFbsSize(const JsepTrack& track, size_t expected) const
{
const JsepVideoCodecDescription* videoCodec = GetVideoCodec(track);
@@ -361,16 +396,210 @@ TEST_F(JsepTrackTest, AudioNegotiation)
TEST_F(JsepTrackTest, VideoNegotiation)
{
Init(SdpMediaSection::kVideo);
OfferAnswer();
CheckOffEncodingCount(1);
CheckAnsEncodingCount(1);
}
+TEST_F(JsepTrackTest, VideoNegotationOffererFEC)
+{
+ mOffCodecs.values = MakeCodecs(true);
+ mAnsCodecs.values = MakeCodecs(false);
+
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+ OfferAnswer();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+
+ ASSERT_NE(mOffer->ToString().find("a=fmtp:122 120/126/123"), std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=fmtp:122"), std::string::npos);
+
+ const JsepVideoCodecDescription* track = nullptr;
+ ASSERT_TRUE((track = GetVideoCodec(*mSendOff)));
+ ASSERT_EQ("120", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mRecvOff)));
+ ASSERT_EQ("120", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mSendAns)));
+ ASSERT_EQ("120", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mRecvAns)));
+ ASSERT_EQ("120", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, VideoNegotationAnswererFEC)
+{
+ mOffCodecs.values = MakeCodecs(false);
+ mAnsCodecs.values = MakeCodecs(true);
+
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+ OfferAnswer();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_EQ(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_EQ(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+
+ ASSERT_EQ(mOffer->ToString().find("a=fmtp:122"), std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=fmtp:122"), std::string::npos);
+
+ const JsepVideoCodecDescription* track = nullptr;
+ ASSERT_TRUE((track = GetVideoCodec(*mSendOff)));
+ ASSERT_EQ("120", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mRecvOff)));
+ ASSERT_EQ("120", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mSendAns)));
+ ASSERT_EQ("120", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mRecvAns)));
+ ASSERT_EQ("120", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, VideoNegotationOffererAnswererFEC)
+{
+ mOffCodecs.values = MakeCodecs(true);
+ mAnsCodecs.values = MakeCodecs(true);
+
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+ OfferAnswer();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+
+ ASSERT_NE(mOffer->ToString().find("a=fmtp:122 120/126/123"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=fmtp:122 120/126/123"), std::string::npos);
+
+ const JsepVideoCodecDescription* track = nullptr;
+ ASSERT_TRUE((track = GetVideoCodec(*mSendOff, 4)));
+ ASSERT_EQ("120", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mRecvOff, 4)));
+ ASSERT_EQ("120", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mSendAns, 4)));
+ ASSERT_EQ("120", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mRecvAns, 4)));
+ ASSERT_EQ("120", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, VideoNegotationOffererAnswererFECPreferred)
+{
+ mOffCodecs.values = MakeCodecs(true, true);
+ mAnsCodecs.values = MakeCodecs(true);
+
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+ OfferAnswer();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+
+ ASSERT_NE(mOffer->ToString().find("a=fmtp:122 120/126/123"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=fmtp:122 120/126/123"), std::string::npos);
+
+ const JsepVideoCodecDescription* track = nullptr;
+ ASSERT_TRUE((track = GetVideoCodec(*mSendOff, 4)));
+ ASSERT_EQ("122", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mRecvOff, 4)));
+ ASSERT_EQ("122", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mSendAns, 4)));
+ ASSERT_EQ("122", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mRecvAns, 4)));
+ ASSERT_EQ("122", track->mDefaultPt);
+}
+
+// Make sure we only put the right things in the fmtp:122 120/.... line
+TEST_F(JsepTrackTest, VideoNegotationOffererAnswererFECMismatch)
+{
+ mOffCodecs.values = MakeCodecs(true, true);
+ mAnsCodecs.values = MakeCodecs(true);
+ // remove h264 from answer codecs
+ ASSERT_EQ("H264", mAnsCodecs.values[3]->mName);
+ mAnsCodecs.values.erase(mAnsCodecs.values.begin()+3);
+
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+ OfferAnswer();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+
+ ASSERT_NE(mOffer->ToString().find("a=fmtp:122 120/126/123"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=fmtp:122 120/123"), std::string::npos);
+
+ const JsepVideoCodecDescription* track = nullptr;
+ ASSERT_TRUE((track = GetVideoCodec(*mSendOff, 3)));
+ ASSERT_EQ("122", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mRecvOff, 3)));
+ ASSERT_EQ("122", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mSendAns, 3)));
+ ASSERT_EQ("122", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mRecvAns, 3)));
+ ASSERT_EQ("122", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, VideoNegotationOffererAnswererFECZeroVP9Codec)
+{
+ mOffCodecs.values = MakeCodecs(true);
+ JsepVideoCodecDescription* vp9 =
+ new JsepVideoCodecDescription("0", "VP9", 90000);
+ vp9->mConstraints.maxFs = 12288;
+ vp9->mConstraints.maxFps = 60;
+ mOffCodecs.values.push_back(vp9);
+
+ ASSERT_EQ(8U, mOffCodecs.values.size());
+ JsepVideoCodecDescription* red =
+ static_cast<JsepVideoCodecDescription*>(mOffCodecs.values[4]);
+ ASSERT_EQ("red", red->mName);
+ // rebuild the redundant encodings with our newly added "wacky" VP9
+ red->mRedundantEncodings.clear();
+ red->UpdateRedundantEncodings(mOffCodecs.values);
+
+ mAnsCodecs.values = MakeCodecs(true);
+
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+ OfferAnswer();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+
+ ASSERT_NE(mOffer->ToString().find("a=fmtp:122 120/126/123/0"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=fmtp:122 120/126/123\r\n"), std::string::npos);
+}
+
TEST_F(JsepTrackTest, VideoNegotiationOfferRemb)
{
InitCodecs();
// enable remb on the offer codecs
((JsepVideoCodecDescription*)mOffCodecs.values[2])->EnableRemb();
InitTracks(SdpMediaSection::kVideo);
InitSdp(SdpMediaSection::kVideo);
OfferAnswer();
--- a/media/webrtc/signaling/test/sdp_unittests.cpp
+++ b/media/webrtc/signaling/test/sdp_unittests.cpp
@@ -1220,24 +1220,26 @@ const std::string kBasicAudioVideoOffer
"a=candidate:6 1 UDP 16515071 162.222.183.171 51858 typ relay raddr 162.222.183.171 rport 51858" CRLF
"a=candidate:3 2 UDP 100401150 162.222.183.171 62454 typ relay raddr 162.222.183.171 rport 62454" CRLF
"a=candidate:2 2 UDP 1694236670 24.6.134.204 55428 typ srflx raddr 10.0.0.36 rport 55428" CRLF
"a=candidate:6 2 UDP 16515070 162.222.183.171 50340 typ relay raddr 162.222.183.171 rport 50340" CRLF
"a=candidate:0 2 UDP 2130379006 10.0.0.36 55428 typ host" CRLF
"a=rtcp:62454 IN IP4 162.222.183.171" CRLF
"a=end-of-candidates" CRLF
"a=ssrc:5150" CRLF
-"m=video 9 RTP/SAVPF 120 121" CRLF
+"m=video 9 RTP/SAVPF 120 121 122 123" CRLF
"c=IN IP6 ::1" CRLF
"a=fingerprint:sha-1 DF:FA:FB:08:3B:3C:54:1D:D7:D4:05:77:A0:72:9B:14:08:6D:0F:4C:2E:AC:8A:FD:0A:8E:99:BF:5D:E8:3C:E7" CRLF
"a=mid:second" CRLF
"a=rtpmap:120 VP8/90000" CRLF
"a=fmtp:120 max-fs=3600;max-fr=30" CRLF
"a=rtpmap:121 VP9/90000" CRLF
"a=fmtp:121 max-fs=3600;max-fr=30" CRLF
+"a=rtpmap:122 red/90000" CRLF
+"a=rtpmap:123 ulpfec/90000" CRLF
"a=recvonly" CRLF
"a=rtcp-fb:120 nack" CRLF
"a=rtcp-fb:120 nack pli" CRLF
"a=rtcp-fb:120 ccm fir" CRLF
"a=rtcp-fb:121 nack" CRLF
"a=rtcp-fb:121 nack pli" CRLF
"a=rtcp-fb:121 ccm fir" CRLF
"a=setup:active" CRLF
@@ -1413,19 +1415,21 @@ TEST_P(NewSdpTest, CheckMlines) {
ASSERT_EQ("101", audio_formats[4]);
ASSERT_EQ(SdpMediaSection::kVideo, mSdp->GetMediaSection(1).GetMediaType())
<< "Wrong type for second media section";
ASSERT_EQ(SdpMediaSection::kRtpSavpf,
mSdp->GetMediaSection(1).GetProtocol())
<< "Wrong protocol for video";
auto video_formats = mSdp->GetMediaSection(1).GetFormats();
- ASSERT_EQ(2U, video_formats.size()) << "Wrong number of formats for video";
+ ASSERT_EQ(4U, video_formats.size()) << "Wrong number of formats for video";
ASSERT_EQ("120", video_formats[0]);
ASSERT_EQ("121", video_formats[1]);
+ ASSERT_EQ("122", video_formats[2]);
+ ASSERT_EQ("123", video_formats[3]);
ASSERT_EQ(SdpMediaSection::kAudio, mSdp->GetMediaSection(2).GetMediaType())
<< "Wrong type for third media section";
}
TEST_P(NewSdpTest, CheckSetup) {
ParseSdp(kBasicAudioVideoOffer);
ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
@@ -1513,33 +1517,138 @@ TEST_P(NewSdpTest, CheckRtpmap) {
CheckRtpmap("101",
SdpRtpmapAttributeList::kOtherCodec,
"telephone-event",
8000,
1,
audiosec.GetFormats()[4],
rtpmap);
- const SdpMediaSection& videosec1 = mSdp->GetMediaSection(1);
+ const SdpMediaSection& videosec = mSdp->GetMediaSection(1);
+ const SdpRtpmapAttributeList videoRtpmap =
+ videosec.GetAttributeList().GetRtpmap();
+ ASSERT_EQ(4U, videoRtpmap.mRtpmaps.size())
+ << "Wrong number of rtpmap attributes for video";
+
CheckRtpmap("120",
SdpRtpmapAttributeList::kVP8,
"VP8",
90000,
0,
- videosec1.GetFormats()[0],
- videosec1.GetAttributeList().GetRtpmap());
-
- const SdpMediaSection& videosec2 = mSdp->GetMediaSection(1);
+ videosec.GetFormats()[0],
+ videoRtpmap);
+
CheckRtpmap("121",
SdpRtpmapAttributeList::kVP9,
"VP9",
90000,
0,
- videosec2.GetFormats()[1],
- videosec2.GetAttributeList().GetRtpmap());
+ videosec.GetFormats()[1],
+ videoRtpmap);
+
+ CheckRtpmap("122",
+ SdpRtpmapAttributeList::kRed,
+ "red",
+ 90000,
+ 0,
+ videosec.GetFormats()[2],
+ videoRtpmap);
+
+ CheckRtpmap("123",
+ SdpRtpmapAttributeList::kUlpfec,
+ "ulpfec",
+ 90000,
+ 0,
+ videosec.GetFormats()[3],
+ videoRtpmap);
+}
+
+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
+ "a=fingerprint:sha-1 DF:FA:FB:08:3B:3C:54:1D:D7:D4:05:77:A0:72:9B:14:08:6D:0F:4C:2E:AC:8A:FD:0A:8E:99:BF:5D:E8:3C:E7" CRLF
+ "a=rtpmap:97 H264/90000" CRLF
+ "a=fmtp:97 profile-level-id=42a01e" CRLF
+ "a=rtpmap:120 VP8/90000" CRLF
+ "a=fmtp:120 max-fs=3600;max-fr=30" CRLF
+ "a=rtpmap:121 VP9/90000" CRLF
+ "a=fmtp:121 max-fs=3600;max-fr=30" CRLF
+ "a=rtpmap:122 red/90000" CRLF
+ "a=rtpmap:123 ulpfec/90000" CRLF;
+
+TEST_P(NewSdpTest, CheckRedNoFmtp) {
+ ParseSdp(kVideoWithRedAndUlpfecSdp);
+ 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 video_format_params =
+ mSdp->GetMediaSection(0).GetAttributeList().GetFmtp().mFmtps;
+ ASSERT_EQ(3U, video_format_params.size());
+
+ // make sure we don't get a fmtp for codec 122
+ for (size_t i = 0; i < video_format_params.size(); ++i) {
+ ASSERT_NE("122", video_format_params[i].format);
+ }
+}
+
+TEST_P(NewSdpTest, CheckRedFmtpWith2Codecs) {
+ ParseSdp(kVideoWithRedAndUlpfecSdp + "a=fmtp:122 120/121" 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 video_format_params =
+ mSdp->GetMediaSection(0).GetAttributeList().GetFmtp().mFmtps;
+ ASSERT_EQ(4U, video_format_params.size());
+
+ ASSERT_EQ("122", video_format_params[3].format);
+ ASSERT_TRUE(!!video_format_params[3].parameters);
+ ASSERT_EQ(SdpRtpmapAttributeList::kRed,
+ video_format_params[3].parameters->codec_type);
+ const SdpFmtpAttributeList::RedParameters* red_parameters(
+ static_cast<SdpFmtpAttributeList::RedParameters*>(
+ video_format_params[3].parameters.get()));
+ ASSERT_EQ(2U, red_parameters->encodings.size());
+ ASSERT_EQ(120U, red_parameters->encodings[0]);
+ ASSERT_EQ(121U, red_parameters->encodings[1]);
+}
+
+TEST_P(NewSdpTest, CheckRedFmtpWith3Codecs) {
+ ParseSdp(kVideoWithRedAndUlpfecSdp + "a=fmtp:122 120/121/123" 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 video_format_params =
+ mSdp->GetMediaSection(0).GetAttributeList().GetFmtp().mFmtps;
+ ASSERT_EQ(4U, video_format_params.size());
+
+ ASSERT_EQ("122", video_format_params[3].format);
+ ASSERT_TRUE(!!video_format_params[3].parameters);
+ ASSERT_EQ(SdpRtpmapAttributeList::kRed,
+ video_format_params[3].parameters->codec_type);
+ const SdpFmtpAttributeList::RedParameters* red_parameters(
+ static_cast<SdpFmtpAttributeList::RedParameters*>(
+ video_format_params[3].parameters.get()));
+ ASSERT_EQ(3U, red_parameters->encodings.size());
+ ASSERT_EQ(120U, red_parameters->encodings[0]);
+ ASSERT_EQ(121U, red_parameters->encodings[1]);
+ ASSERT_EQ(123U, red_parameters->encodings[2]);
}
const std::string kH264AudioVideoOffer =
"v=0" CRLF
"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
"s=SIP Call" CRLF
"c=IN IP4 224.0.0.1/100/12" CRLF
"t=0 0" CRLF
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -398,16 +398,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.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
#ifdef MOZ_WIDGET_GONK