Backed out 18 changesets (bug 1401592, bug 1676855) for causing failures at test_peerConnection_scaleResolution_oldSetParameters.html. CLOSED TREE

Backed out changeset 47c810ec8060 (bug 1676855)
Backed out changeset aba56121e546 (bug 1401592)
Backed out changeset ba525fa85b99 (bug 1401592)
Backed out changeset 00409e6f4685 (bug 1401592)
Backed out changeset bf98bb55e45f (bug 1401592)
Backed out changeset f16c0eb92363 (bug 1401592)
Backed out changeset 3cb1dde9bbbc (bug 1401592)
Backed out changeset a9bfef738d49 (bug 1401592)
Backed out changeset aa6aa10cfd97 (bug 1401592)
Backed out changeset b4752eaae108 (bug 1401592)
Backed out changeset e868d7b3abd8 (bug 1401592)
Backed out changeset ce11d420246c (bug 1401592)
Backed out changeset 01434a8cb2b6 (bug 1401592)
Backed out changeset 154d08dd3bca (bug 1401592)
Backed out changeset fffc015a5dd5 (bug 1401592)
Backed out changeset 9e11ddaf8b3e (bug 1401592)
Backed out changeset 26a812435ddd (bug 1401592)
Backed out changeset 3b064fbc9a61 (bug 1401592)
This commit is contained in:
Butkovits Atila 2022-12-13 22:53:05 +02:00
parent 28f5b1d868
commit 74a43f86ea
77 changed files with 952 additions and 5128 deletions

View File

@ -1443,15 +1443,7 @@ class RTCPeerConnection {
kind = sendTrack.kind;
}
try {
return this._pc.addTransceiver(init, kind, sendTrack);
} catch (e) {
// Exceptions thrown by c++ code do not propagate. In most cases, that's
// fine because we're using Promises, which can be copied. But this is
// not promise-based, so we have to do this sketchy stuff.
const holder = new StructuredCloneHolder(new ClonedErrorHolder(e));
throw holder.deserialize(this._win);
}
return this._pc.addTransceiver(init, kind, sendTrack);
}
addTransceiver(sendTrackOrKind, init) {

View File

@ -6,7 +6,6 @@
#include "api/audio/audio_mixer.h"
#include "api/audio_codecs/builtin_audio_decoder_factory.h"
#include "modules/rtp_rtcp/source/rtp_header_extensions.h"
#include "call/audio_state.h"
#include "common/browser_logging/CSFLog.h"
#include "common/browser_logging/WebRtcLog.h"
@ -491,8 +490,7 @@ void PeerConnectionCtx::ForEachPeerConnection(Function&& aFunction) const {
nsresult PeerConnectionCtx::Initialize() {
initGMP();
SdpRidAttributeList::kMaxRidLength =
webrtc::BaseRtpStringExtension::kMaxValueSizeBytes;
nsresult rv = NS_NewTimerWithFuncCallback(
getter_AddRefs(mTelemetryTimer), EverySecondTelemetryCallback_m, this,
1000, nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP,

View File

@ -477,11 +477,6 @@ nsresult PeerConnectionImpl::Initialize(PeerConnectionObserver& aObserver,
// Initialize the media object.
mForceProxy = ShouldForceProxy();
// We put this here, in case we later want to set this based on a non-standard
// param in RTCConfiguration.
mAllowOldSetParameters = Preferences::GetBool(
"media.peerconnection.allow_old_setParameters", false);
// setup the stun local addresses IPC async call
InitLocalAddrs();
@ -931,7 +926,18 @@ nsresult PeerConnectionImpl::AddRtpTransceiverToJsepSession(
return res;
}
mJsepSession->AddTransceiver(transceiver);
res = mJsepSession->AddTransceiver(transceiver);
if (NS_FAILED(res)) {
std::string errorString = mJsepSession->GetLastError();
CSFLogError(LOGTAG, "%s (%s) : pc = %s, error = %s", __FUNCTION__,
transceiver->GetMediaType() == SdpMediaSection::kAudio
? "audio"
: "video",
mHandle.c_str(), errorString.c_str());
return NS_ERROR_FAILURE;
}
return NS_OK;
}
@ -948,9 +954,6 @@ static Maybe<SdpMediaSection::MediaType> ToSdpMediaType(
already_AddRefed<RTCRtpTransceiver> PeerConnectionImpl::AddTransceiver(
const dom::RTCRtpTransceiverInit& aInit, const nsAString& aKind,
dom::MediaStreamTrack* aSendTrack, ErrorResult& aRv) {
// Copy, because we might need to modify
RTCRtpTransceiverInit init(aInit);
Maybe<SdpMediaSection::MediaType> type = ToSdpMediaType(aKind);
if (NS_WARN_IF(!type.isSome())) {
MOZ_ASSERT(false, "Invalid media kind");
@ -971,93 +974,9 @@ already_AddRefed<RTCRtpTransceiver> PeerConnectionImpl::AddTransceiver(
return nullptr;
}
auto& sendEncodings = init.mSendEncodings;
// CheckAndRectifyEncodings covers these six:
// If any encoding contains a rid member whose value does not conform to the
// grammar requirements specified in Section 10 of [RFC8851], throw a
// TypeError.
// If some but not all encodings contain a rid member, throw a TypeError.
// If any encoding contains a rid member whose value is the same as that of a
// rid contained in another encoding in sendEncodings, throw a TypeError.
// If kind is "audio", remove the scaleResolutionDownBy member from all
// encodings that contain one.
// If any encoding contains a scaleResolutionDownBy member whose value is
// less than 1.0, throw a RangeError.
// Verify that the value of each maxFramerate member in sendEncodings that is
// defined is greater than 0.0. If one of the maxFramerate values does not
// meet this requirement, throw a RangeError.
RTCRtpSender::CheckAndRectifyEncodings(sendEncodings,
*type == SdpMediaSection::kVideo, aRv);
if (aRv.Failed()) {
return nullptr;
}
// If any encoding contains a read-only parameter other than rid, throw an
// InvalidAccessError.
// NOTE: We don't support any additional read-only params right now. Also,
// spec shoehorns this in between checks that setParameters also performs
// (between the rid checks and the scaleResolutionDownBy checks).
// If any encoding contains a scaleResolutionDownBy member, then for each
// encoding without one, add a scaleResolutionDownBy member with the value
// 1.0.
for (const auto& constEncoding : sendEncodings) {
if (constEncoding.mScaleResolutionDownBy.WasPassed()) {
for (auto& encoding : sendEncodings) {
if (!encoding.mScaleResolutionDownBy.WasPassed()) {
encoding.mScaleResolutionDownBy.Construct(1.0f);
}
}
break;
}
}
// Let maxN be the maximum number of total simultaneous encodings the user
// agent may support for this kind, at minimum 1.This should be an optimistic
// number since the codec to be used is not known yet.
size_t maxN =
(*type == SdpMediaSection::kVideo) ? webrtc::kMaxSimulcastStreams : 1;
// If the number of encodings stored in sendEncodings exceeds maxN, then trim
// sendEncodings from the tail until its length is maxN.
// NOTE: Spec has this after all validation steps; even if there are elements
// that we will trim off, we still validate them.
if (sendEncodings.Length() > maxN) {
sendEncodings.TruncateLength(maxN);
}
// If kind is "video" and none of the encodings contain a
// scaleResolutionDownBy member, then for each encoding, add a
// scaleResolutionDownBy member with the value 2^(length of sendEncodings -
// encoding index - 1). This results in smaller-to-larger resolutions where
// the last encoding has no scaling applied to it, e.g. 4:2:1 if the length
// is 3.
// NOTE: The code above ensures that these are all set, or all unset, so we
// can just check the first one.
if (sendEncodings.Length() && *type == SdpMediaSection::kVideo &&
!sendEncodings[0].mScaleResolutionDownBy.WasPassed()) {
double scale = 1.0f;
for (auto it = sendEncodings.rbegin(); it != sendEncodings.rend(); ++it) {
it->mScaleResolutionDownBy.Construct(scale);
scale *= 2;
}
}
// If the number of encodings now stored in sendEncodings is 1, then remove
// any rid member from the lone entry.
if (sendEncodings.Length() == 1) {
sendEncodings[0].mRid.Reset();
}
RefPtr<RTCRtpTransceiver> transceiver = CreateTransceiver(
jsepTransceiver->GetUuid(),
jsepTransceiver->GetMediaType() == SdpMediaSection::kVideo, init,
jsepTransceiver->GetMediaType() == SdpMediaSection::kVideo, aInit,
aSendTrack, aRv);
if (aRv.Failed()) {
@ -1612,22 +1531,6 @@ dom::RTCSdpType ToDomSdpType(JsepSdpType aType) {
MOZ_CRASH("Nonexistent JsepSdpType");
}
JsepSdpType ToJsepSdpType(dom::RTCSdpType aType) {
switch (aType) {
case dom::RTCSdpType::Offer:
return kJsepSdpOffer;
case dom::RTCSdpType::Pranswer:
return kJsepSdpPranswer;
case dom::RTCSdpType::Answer:
return kJsepSdpAnswer;
case dom::RTCSdpType::Rollback:
return kJsepSdpRollback;
case dom::RTCSdpType::EndGuard_:;
}
MOZ_CRASH("Nonexistent dom::RTCSdpType");
}
NS_IMETHODIMP
PeerConnectionImpl::SetLocalDescription(int32_t aAction, const char* aSDP) {
PC_AUTO_ENTER_API_CALL(true);
@ -2084,12 +1987,6 @@ void PeerConnectionImpl::StampTimecard(const char* aEvent) {
STAMP_TIMECARD(mTimeCard, aEvent);
}
void PeerConnectionImpl::SendWarningToConsole(const nsCString& aWarning) {
nsAutoString msg = NS_ConvertASCIItoUTF16(aWarning);
nsContentUtils::ReportToConsoleByWindowID(msg, nsIScriptError::warningFlag,
"WebRTC"_ns, mWindow->WindowID());
}
nsresult PeerConnectionImpl::CalculateFingerprint(
const std::string& algorithm, std::vector<uint8_t>* fingerprint) const {
DtlsDigest digest(algorithm);
@ -2325,21 +2222,6 @@ void PeerConnectionImpl::BreakCycles() {
mTransceivers.Clear();
}
bool PeerConnectionImpl::HasPendingSetParameters() const {
for (const auto& transceiver : mTransceivers) {
if (transceiver->Sender()->HasPendingSetParameters()) {
return true;
}
}
return false;
}
void PeerConnectionImpl::InvalidateLastReturnedParameters() {
for (const auto& transceiver : mTransceivers) {
transceiver->Sender()->InvalidateLastReturnedParameters();
}
}
nsresult PeerConnectionImpl::SetConfiguration(
const RTCConfiguration& aConfiguration) {
nsresult rv = mTransportHandler->SetIceConfig(
@ -2383,7 +2265,6 @@ nsresult PeerConnectionImpl::SetConfiguration(
// Store the configuration for about:webrtc
StoreConfigurationForAboutWebrtc(aConfiguration);
return NS_OK;
}
@ -2544,52 +2425,31 @@ void PeerConnectionImpl::DoSetDescriptionSuccessPostProcessing(
MOZ_ASSERT(mUncommittedJsepSession);
// sRD/sLD needs to be redone in certain circumstances
bool needsRedo = HasPendingSetParameters();
if (!needsRedo && aRemote && (aSdpType == dom::RTCSdpType::Offer)) {
for (auto& transceiver : mTransceivers) {
if (!mUncommittedJsepSession->GetTransceiver(
transceiver->GetJsepTransceiverId())) {
needsRedo = true;
break;
}
}
}
if (needsRedo) {
// Spec says to abort, and re-do the sRD!
// This happens either when there is a SetParameters call in
// flight (that will race against the [[SendEncodings]]
// modification caused by sRD(offer)), or when addTrack has been
// called while sRD(offer) was in progress.
mUncommittedJsepSession.reset(mJsepSession->Clone());
JsepSession::Result result;
if (aRemote) {
mUncommittedJsepSession->SetRemoteDescription(
ToJsepSdpType(aSdpType), mRemoteRequestedSDP);
} else {
mUncommittedJsepSession->SetLocalDescription(
ToJsepSdpType(aSdpType), mLocalRequestedSDP);
}
if (result.mError.isSome()) {
// wat
nsCString error(
"When redoing sRD/sLD because it raced against "
"addTrack or setParameters, we encountered a failure that "
"did not happen "
"the first time. This should never happen. The error was: ");
error += mUncommittedJsepSession->GetLastError().c_str();
aP->MaybeRejectWithOperationError(error);
MOZ_ASSERT(false);
} else {
DoSetDescriptionSuccessPostProcessing(aSdpType, aRemote, aP);
}
return;
}
// Check for transceivers added by addTrack/addTransceiver while
// a sRD/sLD was in progress
for (auto& transceiver : mTransceivers) {
if (!mUncommittedJsepSession->GetTransceiver(
transceiver->GetJsepTransceiverId())) {
if (aSdpType == dom::RTCSdpType::Offer && aRemote) {
// Spec says to abort, and re-do the sRD(offer)!
mUncommittedJsepSession.reset(mJsepSession->Clone());
JsepSession::Result result =
mUncommittedJsepSession->SetRemoteDescription(
kJsepSdpOffer, mRemoteRequestedSDP);
MOZ_ASSERT(!!mUncommittedJsepSession->GetTransceiver(
transceiver->GetJsepTransceiverId()));
if (result.mError.isSome()) {
// wat
aP->MaybeRejectWithOperationError(
"When redoing sRD(offer) because it raced against "
"addTrack, we encountered a failure that did not happen "
"the first time. This should never happen.");
MOZ_ASSERT(false);
} else {
DoSetDescriptionSuccessPostProcessing(aSdpType, aRemote, aP);
}
return;
}
// sLD, or sRD(answer), just make sure the new transceiver is
// added, no need to re-do anything.
mUncommittedJsepSession->AddTransceiver(
@ -2604,11 +2464,6 @@ void PeerConnectionImpl::DoSetDescriptionSuccessPostProcessing(
auto newSignalingState = GetSignalingState();
SyncFromJsep();
if (aRemote || aSdpType == dom::RTCSdpType::Pranswer ||
aSdpType == dom::RTCSdpType::Answer) {
InvalidateLastReturnedParameters();
}
// Section 4.4.1.5 Set the RTCSessionDescription:
if (aSdpType == dom::RTCSdpType::Rollback) {
// - step 4.5.10, type is rollback

View File

@ -511,18 +511,6 @@ class PeerConnectionImpl final
return mPacketDumper;
}
nsString GenerateUUID() const {
std::string result;
if (!mUuidGen->Generate(&result)) {
MOZ_CRASH();
}
return NS_ConvertUTF8toUTF16(result.c_str());
}
bool ShouldAllowOldSetParameters() const { return mAllowOldSetParameters; }
void SendWarningToConsole(const nsCString& aWarning);
private:
virtual ~PeerConnectionImpl();
PeerConnectionImpl(const PeerConnectionImpl& rhs);
@ -772,9 +760,6 @@ class PeerConnectionImpl final
void BreakCycles();
bool HasPendingSetParameters() const;
void InvalidateLastReturnedParameters();
RefPtr<WebrtcCallWrapper> mCall;
// See Bug 1642419, this can be removed when all sites are working with RTX.
@ -825,9 +810,6 @@ class PeerConnectionImpl final
// Used to store the mDNS hostnames that we have registered
std::set<std::string> mRegisteredMDNSHostnames;
// web-compat stopgap
bool mAllowOldSetParameters = false;
// Used to store the mDNS hostnames that we have queried
struct PendingIceCandidate {
std::vector<std::string> mTokenizedCandidate;

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,6 @@
#include "mozilla/dom/RTCStatsReportBinding.h"
#include "mozilla/dom/RTCRtpParametersBinding.h"
#include "RTCStatsReport.h"
#include "jsep/JsepTrack.h"
class nsPIDOMWindowInner;
@ -42,7 +41,6 @@ class RTCRtpSender : public nsISupports, public nsWrapperCache {
MediaTransportHandler* aTransportHandler,
AbstractThread* aCallThread, nsISerialEventTarget* aStsThread,
MediaSessionConduit* aConduit, dom::MediaStreamTrack* aTrack,
const Sequence<RTCRtpEncodingParameters>& aEncodings,
RTCRtpTransceiver* aTransceiver);
// nsISupports
@ -61,14 +59,8 @@ class RTCRtpSender : public nsISupports, public nsWrapperCache {
ErrorResult& aError);
already_AddRefed<Promise> GetStats(ErrorResult& aError);
already_AddRefed<Promise> SetParameters(
const dom::RTCRtpSendParameters& aParameters, ErrorResult& aError);
// Not a simple getter, so not const
// See https://w3c.github.io/webrtc-pc/#dom-rtcrtpsender-getparameters
void GetParameters(RTCRtpSendParameters& aParameters);
static void CheckAndRectifyEncodings(
Sequence<RTCRtpEncodingParameters>& aEncodings, bool aVideo,
ErrorResult& aRv);
const dom::RTCRtpParameters& aParameters, ErrorResult& aError);
void GetParameters(RTCRtpParameters& aParameters) const;
nsPIDOMWindowInner* GetParentObject() const;
nsTArray<RefPtr<RTCStatsPromise>> GetStatsInternal();
@ -80,7 +72,6 @@ class RTCRtpSender : public nsISupports, public nsWrapperCache {
void SetStreams(const Sequence<OwningNonNull<DOMMediaStream>>& aStreams);
// ChromeOnly webidl
void GetStreams(nsTArray<RefPtr<DOMMediaStream>>& aStreams);
// ChromeOnly webidl
void SetTrack(const RefPtr<MediaStreamTrack>& aTrack);
void Shutdown();
void BreakCycles();
@ -95,9 +86,9 @@ class RTCRtpSender : public nsISupports, public nsWrapperCache {
// This is called when we set an answer (ie; when the transport is finalized).
void UpdateTransport();
void UpdateConduit();
void SyncToJsep(JsepTransceiver& aJsepTransceiver) const;
void SyncFromJsep(const JsepTransceiver& aJsepTransceiver);
void MaybeUpdateConduit();
AbstractCanonical<Ssrcs>* CanonicalSsrcs() { return &mSsrcs; }
AbstractCanonical<Ssrcs>* CanonicalVideoRtxSsrcs() { return &mVideoRtxSsrcs; }
@ -121,11 +112,6 @@ class RTCRtpSender : public nsISupports, public nsWrapperCache {
AbstractCanonical<std::string>* CanonicalCname() { return &mCname; }
AbstractCanonical<bool>* CanonicalTransmitting() { return &mTransmitting; }
bool HasPendingSetParameters() const { return mPendingParameters.isSome(); }
void InvalidateLastReturnedParameters() {
mLastReturnedParameters = Nothing();
}
private:
virtual ~RTCRtpSender();
@ -134,107 +120,20 @@ class RTCRtpSender : public nsISupports, public nsWrapperCache {
std::string GetMid() const;
JsepTransceiver& GetJsepTransceiver();
void ApplyParameters(const RTCRtpParameters& aParameters);
void ConfigureVideoCodecMode();
void SetJsepRids(const RTCRtpSendParameters& aParameters);
static void ApplyJsEncodingToConduitEncoding(
const RTCRtpEncodingParameters& aJsEncoding,
VideoCodecConfig::Encoding* aConduitEncoding);
void UpdateRestorableEncodings(
const Sequence<RTCRtpEncodingParameters>& aEncodings);
Sequence<RTCRtpEncodingParameters> GetMatchingEncodings(
const std::vector<std::string>& aRids) const;
Sequence<RTCRtpEncodingParameters> ToSendEncodings(
const std::vector<std::string>& aRids) const;
void MaybeGetJsepRids();
void WarnAboutBadSetParameters(const nsCString& aError);
nsCOMPtr<nsPIDOMWindowInner> mWindow;
RefPtr<PeerConnectionImpl> mPc;
RefPtr<dom::MediaStreamTrack> mSenderTrack;
RTCRtpSendParameters mParameters;
Maybe<RTCRtpSendParameters> mPendingParameters;
uint32_t mNumSetParametersCalls = 0;
// When JSEP goes from simulcast to unicast without a rid, and we started out
// as unicast without a rid, we are supposed to restore that unicast encoding
// from before.
Maybe<RTCRtpEncodingParameters> mUnicastEncoding;
bool mSimulcastEnvelopeSet = false;
Maybe<RTCRtpSendParameters> mLastReturnedParameters;
RTCRtpParameters mParameters;
RefPtr<MediaPipelineTransmit> mPipeline;
RefPtr<RTCRtpTransceiver> mTransceiver;
nsTArray<RefPtr<DOMMediaStream>> mStreams;
bool mHaveSetupTransport = false;
// TODO(bug 1803388): Remove this stuff once it is no longer needed.
bool mAllowOldSetParameters = false;
// TODO(bug 1803388): Remove the glean warnings once they are no longer needed
bool mHaveWarnedBecauseNoGetParameters = false;
bool mHaveWarnedBecauseEncodingCountChange = false;
bool mHaveWarnedBecauseRidChange = false;
bool mHaveWarnedBecauseNoTransactionId = false;
bool mHaveWarnedBecauseStaleTransactionId = false;
// TODO(bug 1803389): Remove the glean errors once they are no longer needed.
bool mHaveFailedBecauseNoGetParameters = false;
bool mHaveFailedBecauseEncodingCountChange = false;
bool mHaveFailedBecauseRidChange = false;
bool mHaveFailedBecauseNoTransactionId = false;
bool mHaveFailedBecauseStaleTransactionId = false;
bool mHaveFailedBecauseNoEncodings = false;
bool mHaveFailedBecauseOtherError = false;
RefPtr<dom::RTCDTMFSender> mDtmf;
class BaseConfig {
public:
// TODO(bug 1744116): Use = default here
bool operator==(const BaseConfig& aOther) const {
return mSsrcs == aOther.mSsrcs &&
mLocalRtpExtensions == aOther.mLocalRtpExtensions &&
mCname == aOther.mCname && mTransmitting == aOther.mTransmitting;
}
Ssrcs mSsrcs;
RtpExtList mLocalRtpExtensions;
std::string mCname;
bool mTransmitting = false;
};
class VideoConfig : public BaseConfig {
public:
// TODO(bug 1744116): Use = default here
bool operator==(const VideoConfig& aOther) const {
return BaseConfig::operator==(aOther) &&
mVideoRtxSsrcs == aOther.mVideoRtxSsrcs &&
mVideoCodec == aOther.mVideoCodec &&
mVideoRtpRtcpConfig == aOther.mVideoRtpRtcpConfig &&
mVideoCodecMode == aOther.mVideoCodecMode;
}
Ssrcs mVideoRtxSsrcs;
Maybe<VideoCodecConfig> mVideoCodec;
Maybe<RtpRtcpConfig> mVideoRtpRtcpConfig;
webrtc::VideoCodecMode mVideoCodecMode =
webrtc::VideoCodecMode::kRealtimeVideo;
};
class AudioConfig : public BaseConfig {
public:
// TODO(bug 1744116): Use = default here
bool operator==(const AudioConfig& aOther) const {
return BaseConfig::operator==(aOther) &&
mAudioCodec == aOther.mAudioCodec && mDtmfPt == aOther.mDtmfPt &&
mDtmfFreq == aOther.mDtmfFreq;
}
Maybe<AudioCodecConfig> mAudioCodec;
int32_t mDtmfPt = -1;
int32_t mDtmfFreq = 0;
};
Maybe<VideoConfig> GetNewVideoConfig();
Maybe<AudioConfig> GetNewAudioConfig();
void UpdateBaseConfig(BaseConfig* aConfig);
void ApplyVideoConfig(const VideoConfig& aConfig);
void ApplyAudioConfig(const AudioConfig& aConfig);
Canonical<Ssrcs> mSsrcs;
Canonical<Ssrcs> mVideoRtxSsrcs;
Canonical<RtpExtList> mLocalRtpExtensions;

View File

@ -219,7 +219,7 @@ void RTCRtpTransceiver::Init(const RTCRtpTransceiverInit& aInit,
mSender = new RTCRtpSender(mWindow, mPc, mTransportHandler,
mCallWrapper->mCallThread, mStsThread, mConduit,
mSendTrack, aInit.mSendEncodings, this);
mSendTrack, this);
if (mConduit) {
InitConduitControl();
@ -236,6 +236,7 @@ void RTCRtpTransceiver::Init(const RTCRtpTransceiverInit& aInit,
self.get(), &RTCRtpTransceiver::UpdateDtlsTransportState);
}));
// TODO(bug 1401592): apply aInit.mSendEncodings to mSender
mSender->SetStreams(aInit.mStreams);
mDirection = aInit.mDirection;
}
@ -395,7 +396,7 @@ nsresult RTCRtpTransceiver::UpdateConduit() {
}
mReceiver->UpdateConduit();
mSender->MaybeUpdateConduit();
mSender->UpdateConduit();
return NS_OK;
}
@ -834,6 +835,7 @@ void RTCRtpTransceiver::NegotiatedDetailsToVideoCodecConfigs(
if (jsepEncoding.HasFormat(video.mDefaultPt)) {
VideoCodecConfig::Encoding encoding;
encoding.rid = jsepEncoding.mRid;
encoding.constraints = jsepEncoding.mConstraints;
config->mEncodings.push_back(encoding);
}
}

View File

@ -136,7 +136,7 @@ class JsepSession {
}
return nullptr;
}
virtual void AddTransceiver(RefPtr<JsepTransceiver> transceiver) = 0;
virtual nsresult AddTransceiver(RefPtr<JsepTransceiver> transceiver) = 0;
class Result {
public:

View File

@ -148,44 +148,45 @@ JsepSessionImpl::GetLocalIceCredentials() const {
return result;
}
void JsepSessionImpl::AddTransceiver(RefPtr<JsepTransceiver> aTransceiver) {
nsresult JsepSessionImpl::AddTransceiver(RefPtr<JsepTransceiver> transceiver) {
mLastError.clear();
MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: Adding transceiver "
<< aTransceiver->GetUuid());
InitTransceiver(*aTransceiver);
#ifdef DEBUG
if (aTransceiver->GetMediaType() == SdpMediaSection::kApplication) {
// Make sure we don't add more than one DataChannel transceiver
for (const auto& transceiver : mTransceivers) {
MOZ_ASSERT(transceiver->GetMediaType() != SdpMediaSection::kApplication);
}
}
#endif
mTransceivers.push_back(aTransceiver);
}
MOZ_MTLOG(ML_DEBUG,
"[" << mName << "]: Adding transceiver " << transceiver->GetUuid());
void JsepSessionImpl::InitTransceiver(JsepTransceiver& aTransceiver) {
mLastError.clear();
if (aTransceiver.GetMediaType() != SdpMediaSection::kApplication) {
if (transceiver->GetMediaType() != SdpMediaSection::kApplication) {
// Make sure we have an ssrc. Might already be set.
aTransceiver.mSendTrack.EnsureSsrcs(mSsrcGenerator, 1U);
aTransceiver.mSendTrack.SetCNAME(mCNAME);
transceiver->mSendTrack.EnsureSsrcs(mSsrcGenerator, 1U);
transceiver->mSendTrack.SetCNAME(mCNAME);
// Make sure we have identifiers for send track, just in case.
// (man I hate this)
if (mEncodeTrackId) {
aTransceiver.mSendTrack.SetTrackId(aTransceiver.GetUuid());
std::string trackId;
// TODO: Maybe reuse the transceiver's UUID here?
if (!mUuidGen->Generate(&trackId)) {
JSEP_SET_ERROR("Failed to generate UUID for JsepTrack");
return NS_ERROR_FAILURE;
}
transceiver->mSendTrack.SetTrackId(trackId);
}
} else {
// Datachannel transceivers should always be sendrecv. Just set it instead
// of asserting.
aTransceiver.mJsDirection = SdpDirectionAttribute::kSendrecv;
transceiver->mJsDirection = SdpDirectionAttribute::kSendrecv;
#ifdef DEBUG
for (const auto& transceiver : mTransceivers) {
MOZ_ASSERT(transceiver->GetMediaType() != SdpMediaSection::kApplication);
}
#endif
}
aTransceiver.mSendTrack.PopulateCodecs(mSupportedCodecs);
aTransceiver.mRecvTrack.PopulateCodecs(mSupportedCodecs);
transceiver->mSendTrack.PopulateCodecs(mSupportedCodecs);
transceiver->mRecvTrack.PopulateCodecs(mSupportedCodecs);
// We do not set mLevel yet, we do that either on createOffer, or setRemote
mTransceivers.push_back(transceiver);
return NS_OK;
}
nsresult JsepSessionImpl::SetBundlePolicy(JsepBundlePolicy policy) {
@ -1564,7 +1565,6 @@ JsepTransceiver* JsepSessionImpl::GetTransceiverForLocal(size_t level) {
if (newTransceiver) {
newTransceiver->SetLevel(level);
transceiver->ClearLevel();
transceiver->mSendTrack.ClearRids();
return newTransceiver;
}
}
@ -1603,7 +1603,6 @@ JsepTransceiver* JsepSessionImpl::GetTransceiverForRemote(
}
transceiver->Disassociate();
transceiver->ClearLevel();
transceiver->mSendTrack.ClearRids();
}
// No transceiver for |level|
@ -1620,7 +1619,8 @@ JsepTransceiver* JsepSessionImpl::GetTransceiverForRemote(
msection.GetMediaType(), *mUuidGen, SdpDirectionAttribute::kRecvonly));
newTransceiver->SetLevel(level);
newTransceiver->SetCreatedBySetRemote();
AddTransceiver(newTransceiver);
nsresult rv = AddTransceiver(newTransceiver);
NS_ENSURE_SUCCESS(rv, nullptr);
return newTransceiver.get();
}
@ -1671,8 +1671,6 @@ nsresult JsepSessionImpl::UpdateTransceiversFromRemoteDescription(
continue;
}
transceiver->mSendTrack.SendTrackSetRemote(mSsrcGenerator, msection);
// Interop workaround for endpoints that don't support msid.
// Ensures that there is a default stream id set, provided the remote is
// sending.
@ -1682,7 +1680,7 @@ nsresult JsepSessionImpl::UpdateTransceiversFromRemoteDescription(
// This will process a=msid if present, or clear the stream ids if the
// msection is not sending. If the msection is sending, and there are no
// a=msid, the previously set default will stay.
transceiver->mRecvTrack.RecvTrackSetRemote(remote, msection);
transceiver->mRecvTrack.UpdateRecvTrack(remote, msection);
}
return NS_OK;
@ -1718,7 +1716,8 @@ void JsepSessionImpl::RollbackLocalOffer() {
RefPtr<JsepTransceiver> temp(
new JsepTransceiver(transceiver->GetMediaType(), *mUuidGen));
InitTransceiver(*temp);
temp->mSendTrack.PopulateCodecs(mSupportedCodecs);
temp->mRecvTrack.PopulateCodecs(mSupportedCodecs);
transceiver->Rollback(*temp, false);
mOldTransceivers.push_back(transceiver);
}
@ -1744,7 +1743,8 @@ void JsepSessionImpl::RollbackRemoteOffer() {
// up at the starting state.
RefPtr<JsepTransceiver> temp(
new JsepTransceiver(transceiver->GetMediaType(), *mUuidGen));
InitTransceiver(*temp);
temp->mSendTrack.PopulateCodecs(mSupportedCodecs);
temp->mRecvTrack.PopulateCodecs(mSupportedCodecs);
transceiver->Rollback(*temp, true);
if (shouldRemove) {

View File

@ -176,7 +176,7 @@ class JsepSessionImpl : public JsepSession, public JsepSessionCopyableStuff {
return mTransceivers;
}
virtual void AddTransceiver(RefPtr<JsepTransceiver> transceiver) override;
virtual nsresult AddTransceiver(RefPtr<JsepTransceiver> transceiver) override;
virtual bool CheckNegotiationNeeded() const override;
@ -259,8 +259,6 @@ class JsepSessionImpl : public JsepSession, public JsepSessionCopyableStuff {
const Sdp* GetAnswer() const;
void SetIceRestarting(bool restarting);
void InitTransceiver(JsepTransceiver& aTransceiver);
// !!!NOT INDEXED BY LEVEL!!! The level mapping is done with
// JsepTransceiver::mLevel. The keys are UUIDs.
std::vector<RefPtr<JsepTransceiver>> mTransceivers;

View File

@ -78,12 +78,12 @@ void JsepTrack::AddToOffer(SsrcGenerator& ssrcGenerator,
AddToMsection(mPrototypeCodecs, offer);
if (mDirection == sdp::kSend) {
std::vector<std::string> rids;
std::vector<JsConstraints> constraints;
if (offer->IsSending()) {
rids = mRids;
constraints = mJsEncodeConstraints;
}
AddToMsection(rids, sdp::kSend, ssrcGenerator,
AddToMsection(constraints, sdp::kSend, ssrcGenerator,
IsRtxEnabled(mPrototypeCodecs), offer);
}
}
@ -102,139 +102,40 @@ void JsepTrack::AddToAnswer(const SdpMediaSection& offer,
AddToMsection(codecs, answer);
if (mDirection == sdp::kSend) {
AddToMsection(mRids, sdp::kSend, ssrcGenerator, IsRtxEnabled(codecs),
std::vector<JsConstraints> constraints;
if (answer->IsSending()) {
constraints = mJsEncodeConstraints;
std::vector<std::pair<SdpRidAttributeList::Rid, bool>> rids;
GetRids(offer, sdp::kRecv, &rids);
NegotiateRids(rids, &constraints);
}
AddToMsection(constraints, sdp::kSend, ssrcGenerator, IsRtxEnabled(codecs),
answer);
}
}
void JsepTrack::SetRids(const std::vector<std::string>& aRids) {
MOZ_ASSERT(!aRids.empty());
if (!mRids.empty()) {
return;
}
mRids = aRids;
}
bool JsepTrack::SetJsConstraints(
const std::vector<JsConstraints>& constraintsList) {
bool constraintsChanged = mJsEncodeConstraints != constraintsList;
mJsEncodeConstraints = constraintsList;
void JsepTrack::SetMaxEncodings(size_t aMax) {
mMaxEncodings = aMax;
if (mRids.size() > mMaxEncodings) {
mRids.resize(mMaxEncodings);
}
}
// Also update negotiated details with constraints, as these can change
// without negotiation.
void JsepTrack::RecvTrackSetRemote(const Sdp& aSdp,
const SdpMediaSection& aMsection) {
mInHaveRemote = true;
MOZ_ASSERT(mDirection == sdp::kRecv);
MOZ_ASSERT(aMsection.GetMediaType() !=
SdpMediaSection::MediaType::kApplication);
std::string error;
SdpHelper helper(&error);
mRemoteSetSendBit = aMsection.IsSending();
if (aMsection.IsSending()) {
(void)helper.GetIdsFromMsid(aSdp, aMsection, &mStreamIds);
} else {
mStreamIds.clear();
if (!mNegotiatedDetails) {
return constraintsChanged;
}
// We do this whether or not the track is active
SetCNAME(helper.GetCNAME(aMsection));
mSsrcs.clear();
if (aMsection.GetAttributeList().HasAttribute(SdpAttribute::kSsrcAttribute)) {
for (const auto& ssrcAttr : aMsection.GetAttributeList().GetSsrc().mSsrcs) {
mSsrcs.push_back(ssrcAttr.ssrc);
}
}
// Use FID ssrc-group to associate rtx ssrcs with "regular" ssrcs. Despite
// not being part of RFC 4588, this is how rtx is negotiated by libwebrtc
// and jitsi.
mSsrcToRtxSsrc.clear();
if (aMsection.GetAttributeList().HasAttribute(
SdpAttribute::kSsrcGroupAttribute)) {
for (const auto& group :
aMsection.GetAttributeList().GetSsrcGroup().mSsrcGroups) {
if (group.semantics == SdpSsrcGroupAttributeList::kFid &&
group.ssrcs.size() == 2) {
// Ensure we have a "regular" ssrc for each rtx ssrc.
if (std::find(mSsrcs.begin(), mSsrcs.end(), group.ssrcs[0]) !=
mSsrcs.end()) {
mSsrcToRtxSsrc[group.ssrcs[0]] = group.ssrcs[1];
// Remove rtx ssrcs from mSsrcs
auto res = std::remove_if(
mSsrcs.begin(), mSsrcs.end(),
[group](uint32_t ssrc) { return ssrc == group.ssrcs[1]; });
mSsrcs.erase(res, mSsrcs.end());
}
for (auto& encoding : mNegotiatedDetails->mEncodings) {
for (const JsConstraints& jsConstraints : mJsEncodeConstraints) {
if (jsConstraints.rid == encoding->mRid) {
encoding->mConstraints = jsConstraints.constraints;
}
}
}
}
void JsepTrack::SendTrackSetRemote(SsrcGenerator& aSsrcGenerator,
const SdpMediaSection& aRemoteMsection) {
mInHaveRemote = true;
if (mType == SdpMediaSection::kApplication) {
return;
}
std::vector<SdpRidAttributeList::Rid> rids;
// TODO: Current language in webrtc-pc is completely broken, and so I will
// not be quoting it here.
if ((mType == SdpMediaSection::kVideo) &&
aRemoteMsection.GetAttributeList().HasAttribute(
SdpAttribute::kSimulcastAttribute)) {
// Note: webrtc-pc does not appear to support the full IETF simulcast
// spec. In particular, the IETF simulcast spec supports requesting
// multiple different sets of encodings. For example, "a=simulcast:send
// 1,2;3,4;5,6" means that there are three simulcast streams, the first of
// which can use either rid 1 or 2 (but not both), the second of which can
// use rid 3 or 4 (but not both), and the third of which can use rid 5 or
// 6 (but not both). webrtc-pc does not support this either/or stuff for
// rid; each simulcast stream gets exactly one rid.
// Also, webrtc-pc does not support the '~' pause syntax at all
// See https://github.com/w3c/webrtc-pc/issues/2769
GetRids(aRemoteMsection, sdp::kRecv, &rids);
}
if (mRids.empty()) {
// Initial configuration
for (const auto& ridAttr : rids) {
// The sipcc-based parser will detect this problem earlier on, but right
// now the rust-based parser will not. So, we do a little bit of
// belt-and-suspenders here.
std::string dummy;
if (SdpRidAttributeList::CheckRidValidity(ridAttr.id, &dummy)) {
mRids.push_back(ridAttr.id);
}
}
if (mRids.size() > mMaxEncodings) {
mRids.resize(mMaxEncodings);
}
} else {
// JSEP is allowed to remove or reorder rids. RTCRtpSender won't pay
// attention to reordering.
std::vector<std::string> newRids;
for (const auto& ridAttr : rids) {
for (const auto& oldRid : mRids) {
if (oldRid == ridAttr.id) {
newRids.push_back(oldRid);
break;
}
}
}
mRids = std::move(newRids);
}
if (mRids.empty()) {
mRids.push_back("");
}
UpdateSsrcs(aSsrcGenerator, mRids.size());
return constraintsChanged;
}
void JsepTrack::AddToMsection(
@ -259,16 +160,33 @@ void JsepTrack::AddToMsection(
}
}
// Updates the |id| values in |constraintsList| with the rid values in |rids|,
// where necessary.
void JsepTrack::NegotiateRids(
const std::vector<std::pair<SdpRidAttributeList::Rid, bool>>& rids,
std::vector<JsConstraints>* constraintsList) const {
for (const auto& ridAndPaused : rids) {
if (!FindConstraints(ridAndPaused.first.id, *constraintsList)) {
// Pair up the first JsConstraints with an empty id, if it exists.
JsConstraints* constraints = FindConstraints("", *constraintsList);
if (constraints) {
constraints->rid = ridAndPaused.first.id;
constraints->paused = ridAndPaused.second;
}
}
}
}
void JsepTrack::UpdateSsrcs(SsrcGenerator& ssrcGenerator, size_t encodings) {
MOZ_ASSERT(mDirection == sdp::kSend);
MOZ_ASSERT(mType != SdpMediaSection::kApplication);
size_t numSsrcs = std::max<size_t>(encodings, 1U);
// Right now, the spec does not permit changing the number of encodings after
// the initial creation of the sender, so we don't need to worry about things
// like a new encoding inserted in between two pre-existing encodings.
EnsureSsrcs(ssrcGenerator, numSsrcs);
PruneSsrcs(numSsrcs);
if (mNegotiatedDetails && mNegotiatedDetails->GetEncodingCount() > numSsrcs) {
mNegotiatedDetails->TruncateEncodings(numSsrcs);
}
MOZ_ASSERT(!mSsrcs.empty());
}
@ -301,36 +219,39 @@ bool JsepTrack::IsRtxEnabled(
return false;
}
void JsepTrack::AddToMsection(const std::vector<std::string>& aRids,
void JsepTrack::AddToMsection(const std::vector<JsConstraints>& constraintsList,
sdp::Direction direction,
SsrcGenerator& ssrcGenerator, bool rtxEnabled,
SdpMediaSection* msection) {
if (aRids.size() > 1) {
UniquePtr<SdpSimulcastAttribute> simulcast(new SdpSimulcastAttribute);
UniquePtr<SdpRidAttributeList> ridAttrs(new SdpRidAttributeList);
for (const std::string& rid : aRids) {
SdpRidAttributeList::Rid ridAttr;
ridAttr.id = rid;
ridAttr.direction = direction;
ridAttrs->mRids.push_back(ridAttr);
UniquePtr<SdpSimulcastAttribute> simulcast(new SdpSimulcastAttribute);
UniquePtr<SdpRidAttributeList> rids(new SdpRidAttributeList);
for (const JsConstraints& constraints : constraintsList) {
if (!constraints.rid.empty()) {
SdpRidAttributeList::Rid rid;
rid.id = constraints.rid;
rid.direction = direction;
rids->mRids.push_back(rid);
SdpSimulcastAttribute::Version version;
version.choices.push_back(SdpSimulcastAttribute::Encoding(rid, false));
version.choices.push_back(
SdpSimulcastAttribute::Encoding(constraints.rid, false));
if (direction == sdp::kSend) {
simulcast->sendVersions.push_back(version);
} else {
simulcast->recvVersions.push_back(version);
}
}
}
if (rids->mRids.size() > 1) {
msection->GetAttributeList().SetAttribute(simulcast.release());
msection->GetAttributeList().SetAttribute(ridAttrs.release());
msection->GetAttributeList().SetAttribute(rids.release());
}
bool requireRtxSsrcs = rtxEnabled && msection->IsSending();
if (mType != SdpMediaSection::kApplication && mDirection == sdp::kSend) {
UpdateSsrcs(ssrcGenerator, aRids.size());
UpdateSsrcs(ssrcGenerator, constraintsList.size());
if (requireRtxSsrcs) {
MOZ_ASSERT(mSsrcs.size() == mSsrcToRtxSsrc.size());
@ -350,9 +271,9 @@ void JsepTrack::AddToMsection(const std::vector<std::string>& aRids,
}
}
void JsepTrack::GetRids(const SdpMediaSection& msection,
sdp::Direction direction,
std::vector<SdpRidAttributeList::Rid>* rids) const {
void JsepTrack::GetRids(
const SdpMediaSection& msection, sdp::Direction direction,
std::vector<std::pair<SdpRidAttributeList::Rid, bool>>* rids) const {
rids->clear();
if (!msection.GetAttributeList().HasAttribute(
SdpAttribute::kSimulcastAttribute)) {
@ -376,19 +297,25 @@ void JsepTrack::GetRids(const SdpMediaSection& msection,
return;
}
// RFC 8853 does not seem to forbid duplicate rids in a simulcast attribute.
// So, while this is obviously silly, we should be prepared for it and
// ignore those duplicate rids.
std::set<std::string> uniqueRids;
for (const SdpSimulcastAttribute::Version& version : *versions) {
if (!version.choices.empty() && !uniqueRids.count(version.choices[0].rid)) {
if (!version.choices.empty()) {
// We validate that rids are present (and sane) elsewhere.
rids->push_back(*msection.FindRid(version.choices[0].rid));
uniqueRids.insert(version.choices[0].rid);
rids->push_back(std::make_pair(*msection.FindRid(version.choices[0].rid),
version.choices[0].paused));
}
}
}
JsepTrack::JsConstraints* JsepTrack::FindConstraints(
const std::string& id, std::vector<JsConstraints>& constraintsList) const {
for (JsConstraints& constraints : constraintsList) {
if (constraints.rid == id) {
return &constraints;
}
}
return nullptr;
}
void JsepTrack::CreateEncodings(
const SdpMediaSection& remote,
const std::vector<UniquePtr<JsepCodecDescription>>& negotiatedCodecs,
@ -406,28 +333,51 @@ void JsepTrack::CreateEncodings(
// TODO add support for b=AS if TIAS is not set (bug 976521)
if (mRids.empty()) {
mRids.push_back("");
std::vector<std::pair<SdpRidAttributeList::Rid, bool>> rids;
GetRids(remote, sdp::kRecv, &rids); // Get rids we will send
NegotiateRids(rids, &mJsEncodeConstraints);
if (rids.empty()) {
// Add dummy value with an empty id to make sure we get a single unicast
// stream.
rids.push_back(std::make_pair(SdpRidAttributeList::Rid(), false));
}
size_t numEncodings = mRids.size();
size_t max_streams = 1;
// Drop SSRCs if fewer RIDs were offered than we have encodings
if (mSsrcs.size() > numEncodings) {
PruneSsrcs(numEncodings);
if (!mJsEncodeConstraints.empty()) {
max_streams = std::min(rids.size(), mJsEncodeConstraints.size());
}
// Drop SSRCs if less RIDs were offered than we have encoding constraints
// Just in case.
if (mSsrcs.size() > max_streams) {
PruneSsrcs(max_streams);
}
// For each stream make sure we have an encoding, and configure
// that encoding appropriately.
for (size_t i = 0; i < numEncodings; ++i) {
UniquePtr<JsepTrackEncoding> encoding(new JsepTrackEncoding);
if (mRids.size() > i) {
encoding->mRid = mRids[i];
for (size_t i = 0; i < max_streams; ++i) {
if (i == negotiatedDetails->mEncodings.size()) {
negotiatedDetails->mEncodings.emplace_back(new JsepTrackEncoding);
}
auto& encoding = negotiatedDetails->mEncodings[i];
for (const auto& codec : negotiatedCodecs) {
encoding->AddCodec(*codec);
if (rids[i].first.HasFormat(codec->mDefaultPt)) {
encoding->AddCodec(*codec);
}
}
encoding->mRid = rids[i].first.id;
encoding->mPaused = rids[i].second;
// If we end up supporting params for rid, we would handle that here.
// Incorporate the corresponding JS encoding constraints, if they exist
for (const JsConstraints& jsConstraints : mJsEncodeConstraints) {
if (jsConstraints.rid == rids[i].first.id) {
encoding->mConstraints = jsConstraints.constraints;
}
}
negotiatedDetails->mEncodings.push_back(std::move(encoding));
}
}
@ -602,7 +552,6 @@ void JsepTrack::Negotiate(const SdpMediaSection& answer,
}
}
mInHaveRemote = false;
mNegotiatedDetails = std::move(negotiatedDetails);
}

View File

@ -46,14 +46,6 @@ class JsepTrackNegotiatedDetails {
return *mEncodings[index];
}
void TruncateEncodings(size_t aSize) {
if (mEncodings.size() < aSize) {
MOZ_ASSERT(false);
return;
}
mEncodings.resize(aSize);
}
const SdpExtmapAttributeList::Extmap* GetExt(
const std::string& ext_name) const {
auto it = mExtmap.find(ext_name);
@ -107,12 +99,56 @@ class JsepTrack {
void ClearStreamIds() { mStreamIds.clear(); }
void RecvTrackSetRemote(const Sdp& aSdp, const SdpMediaSection& aMsection);
void UpdateRecvTrack(const Sdp& sdp, const SdpMediaSection& msection) {
MOZ_ASSERT(mDirection == sdp::kRecv);
MOZ_ASSERT(msection.GetMediaType() !=
SdpMediaSection::MediaType::kApplication);
std::string error;
SdpHelper helper(&error);
// This is called whenever a remote description is set; we do not wait for
// offer/answer to complete, since there's nothing to actually negotiate here.
void SendTrackSetRemote(SsrcGenerator& aSsrcGenerator,
const SdpMediaSection& aRemoteMsection);
mRemoteSetSendBit = msection.IsSending();
if (msection.IsSending()) {
(void)helper.GetIdsFromMsid(sdp, msection, &mStreamIds);
} else {
mStreamIds.clear();
}
// We do this whether or not the track is active
SetCNAME(helper.GetCNAME(msection));
mSsrcs.clear();
if (msection.GetAttributeList().HasAttribute(
SdpAttribute::kSsrcAttribute)) {
for (auto& ssrcAttr : msection.GetAttributeList().GetSsrc().mSsrcs) {
mSsrcs.push_back(ssrcAttr.ssrc);
}
}
// Use FID ssrc-group to associate rtx ssrcs with "regular" ssrcs. Despite
// not being part of RFC 4588, this is how rtx is negotiated by libwebrtc
// and jitsi.
mSsrcToRtxSsrc.clear();
if (msection.GetAttributeList().HasAttribute(
SdpAttribute::kSsrcGroupAttribute)) {
for (const auto& group :
msection.GetAttributeList().GetSsrcGroup().mSsrcGroups) {
if (group.semantics == SdpSsrcGroupAttributeList::kFid &&
group.ssrcs.size() == 2) {
// Ensure we have a "regular" ssrc for each rtx ssrc.
if (std::find(mSsrcs.begin(), mSsrcs.end(), group.ssrcs[0]) !=
mSsrcs.end()) {
mSsrcToRtxSsrc[group.ssrcs[0]] = group.ssrcs[1];
// Remove rtx ssrcs from mSsrcs
auto res = std::remove_if(
mSsrcs.begin(), mSsrcs.end(),
[group](uint32_t ssrc) { return ssrc == group.ssrcs[1]; });
mSsrcs.erase(res, mSsrcs.end());
}
}
}
}
}
JsepTrack(const JsepTrack& orig) { *this = orig; }
@ -126,13 +162,11 @@ class JsepTrack {
mTrackId = rhs.mTrackId;
mCNAME = rhs.mCNAME;
mDirection = rhs.mDirection;
mRids = rhs.mRids;
mJsEncodeConstraints = rhs.mJsEncodeConstraints;
mSsrcs = rhs.mSsrcs;
mSsrcToRtxSsrc = rhs.mSsrcToRtxSsrc;
mActive = rhs.mActive;
mRemoteSetSendBit = rhs.mRemoteSetSendBit;
mMaxEncodings = rhs.mMaxEncodings;
mRtxIsAllowed = rhs.mRtxIsAllowed;
mPrototypeCodecs.clear();
for (const auto& codec : rhs.mPrototypeCodecs) {
@ -224,20 +258,31 @@ class JsepTrack {
virtual void ClearNegotiatedDetails() { mNegotiatedDetails.reset(); }
void SetRids(const std::vector<std::string>& aRids);
void ClearRids() { mRids.clear(); }
const std::vector<std::string>& GetRids() const { return mRids; }
struct JsConstraints {
std::string rid;
bool paused = false;
EncodingConstraints constraints;
bool operator==(const JsConstraints& other) const {
return rid == other.rid && paused == other.paused &&
constraints == other.constraints;
}
};
void AddToMsection(const std::vector<std::string>& aRids,
// Returns true if the constraints changed.
bool SetJsConstraints(const std::vector<JsConstraints>& constraintsList);
void GetJsConstraints(std::vector<JsConstraints>* outConstraintsList) const {
MOZ_ASSERT(outConstraintsList);
*outConstraintsList = mJsEncodeConstraints;
}
void AddToMsection(const std::vector<JsConstraints>& constraintsList,
sdp::Direction direction, SsrcGenerator& ssrcGenerator,
bool rtxEnabled, SdpMediaSection* msection);
// See Bug 1642419, this can be removed when all sites are working with RTX.
void SetRtxIsAllowed(bool aRtxIsAllowed) { mRtxIsAllowed = aRtxIsAllowed; }
void SetMaxEncodings(size_t aMax);
bool IsInHaveRemote() const { return mInHaveRemote; }
private:
std::vector<UniquePtr<JsepCodecDescription>> GetCodecClones() const;
static void EnsureNoDuplicatePayloadTypes(
@ -247,8 +292,9 @@ class JsepTrack {
std::vector<uint16_t>* pts);
void AddToMsection(const std::vector<UniquePtr<JsepCodecDescription>>& codecs,
SdpMediaSection* msection);
void GetRids(const SdpMediaSection& msection, sdp::Direction direction,
std::vector<SdpRidAttributeList::Rid>* rids) const;
void GetRids(
const SdpMediaSection& msection, sdp::Direction direction,
std::vector<std::pair<SdpRidAttributeList::Rid, bool>>* rids) const;
void CreateEncodings(
const SdpMediaSection& remote,
const std::vector<UniquePtr<JsepCodecDescription>>& negotiatedCodecs,
@ -258,6 +304,12 @@ class JsepTrack {
const SdpMediaSection& remote, bool remoteIsOffer,
Maybe<const SdpMediaSection&> local);
JsConstraints* FindConstraints(
const std::string& rid,
std::vector<JsConstraints>& constraintsList) const;
void NegotiateRids(
const std::vector<std::pair<SdpRidAttributeList::Rid, bool>>& rids,
std::vector<JsConstraints>* constraints) const;
void UpdateSsrcs(SsrcGenerator& ssrcGenerator, size_t encodings);
void PruneSsrcs(size_t aNumSsrcs);
bool IsRtxEnabled(
@ -270,20 +322,15 @@ class JsepTrack {
std::string mCNAME;
sdp::Direction mDirection;
std::vector<UniquePtr<JsepCodecDescription>> mPrototypeCodecs;
// List of rids. May be initially populated from JS, or from a remote SDP.
// Can be updated by remote SDP. If no negotiation has taken place at all,
// this will be empty. If negotiation has taken place, but no simulcast
// attr was negotiated, this will contain the empty string as a single
// element. If a simulcast attribute was negotiated, this will contain the
// negotiated rids.
std::vector<std::string> mRids;
// Holds encoding params/constraints from JS. Simulcast happens when there are
// multiple of these. If there are none, we assume unconstrained unicast with
// no rid.
std::vector<JsConstraints> mJsEncodeConstraints;
UniquePtr<JsepTrackNegotiatedDetails> mNegotiatedDetails;
std::vector<uint32_t> mSsrcs;
std::map<uint32_t, uint32_t> mSsrcToRtxSsrc;
bool mActive;
bool mRemoteSetSendBit;
size_t mMaxEncodings = 3;
bool mInHaveRemote = false;
// See Bug 1642419, this can be removed when all sites are working with RTX.
bool mRtxIsAllowed = true;

View File

@ -20,19 +20,11 @@ namespace mozilla {
class JsepTrackEncoding {
public:
JsepTrackEncoding() = default;
JsepTrackEncoding(const JsepTrackEncoding& orig) { *this = orig; }
JsepTrackEncoding(JsepTrackEncoding&& aOrig) = default;
JsepTrackEncoding& operator=(const JsepTrackEncoding& aRhs) {
if (this != &aRhs) {
mRid = aRhs.mRid;
mCodecs.clear();
for (const auto& codec : aRhs.mCodecs) {
mCodecs.emplace_back(codec->Clone());
}
JsepTrackEncoding(const JsepTrackEncoding& orig)
: mConstraints(orig.mConstraints), mRid(orig.mRid) {
for (const auto& codec : orig.mCodecs) {
mCodecs.emplace_back(codec->Clone());
}
return *this;
}
const std::vector<UniquePtr<JsepCodecDescription>>& GetCodecs() const {
@ -52,7 +44,9 @@ class JsepTrackEncoding {
return false;
}
EncodingConstraints mConstraints;
std::string mRid;
bool mPaused = false;
private:
std::vector<UniquePtr<JsepCodecDescription>> mCodecs;

View File

@ -70,15 +70,14 @@ class JsepTransceiver {
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(JsepTransceiver);
void Rollback(JsepTransceiver& oldTransceiver, bool aRemote) {
void Rollback(JsepTransceiver& oldTransceiver, bool rollbackLevel) {
MOZ_ASSERT(oldTransceiver.GetMediaType() == GetMediaType());
MOZ_ASSERT(!oldTransceiver.IsNegotiated() || !oldTransceiver.HasLevel() ||
!HasLevel() || oldTransceiver.GetLevel() == GetLevel());
mTransport = oldTransceiver.mTransport;
if (aRemote) {
if (rollbackLevel) {
mLevel = oldTransceiver.mLevel;
mBundleLevel = oldTransceiver.mBundleLevel;
mSendTrack = oldTransceiver.mSendTrack;
}
mRecvTrack = oldTransceiver.mRecvTrack;

View File

@ -121,11 +121,8 @@ class VideoCodecConfig {
struct Encoding {
std::string rid;
EncodingConstraints constraints;
bool active = true;
// TODO(bug 1744116): Use = default here
bool operator==(const Encoding& aOther) const {
return rid == aOther.rid && constraints == aOther.constraints &&
active == aOther.active;
return rid == aOther.rid && constraints == aOther.constraints;
}
};
std::vector<Encoding> mEncodings;
@ -136,7 +133,6 @@ class VideoCodecConfig {
uint8_t mPacketizationMode;
// TODO: add external negotiated SPS/PPS
// TODO(bug 1744116): Use = default here
bool operator==(const VideoCodecConfig& aRhs) const {
return mType == aRhs.mType && mName == aRhs.mName &&
mAckFbTypes == aRhs.mAckFbTypes &&

View File

@ -1309,22 +1309,6 @@ MediaConduitErrorCode WebrtcVideoConduit::SendVideoFrame(
__FUNCTION__);
return kMediaConduitNoError;
}
// Workaround for bug in libwebrtc where all encodings are transmitted
// if they are all inactive.
bool anyActive = false;
for (const auto& encoding : mCurSendCodecConfig->mEncodings) {
if (encoding.active) {
anyActive = true;
break;
}
}
if (!anyActive) {
CSFLogVerbose(LOGTAG, "WebrtcVideoConduit %p %s No active encodings",
this, __FUNCTION__);
return kMediaConduitNoError;
}
CSFLogVerbose(LOGTAG, "WebrtcVideoConduit %p %s (send SSRC %u (0x%x))",
this, __FUNCTION__, mSendStreamConfig.rtp.ssrcs.front(),
mSendStreamConfig.rtp.ssrcs.front());

View File

@ -150,7 +150,6 @@ std::vector<webrtc::VideoStream> VideoStreamFactory::CreateEncoderStreams(
for (int idx = streamCount - 1; idx >= 0; --idx) {
webrtc::VideoStream video_stream;
auto& encoding = mCodecConfig.mEncodings[idx];
video_stream.active = encoding.active;
MOZ_ASSERT(encoding.constraints.scaleDownBy >= 1.0);
gfx::IntSize newSize(0, 0);

View File

@ -1,264 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# Adding a new metric? We have docs for that!
# https://firefox-source-docs.mozilla.org/toolkit/components/glean/user/new_definitions_file.html
---
$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0
$tags:
- 'Core :: WebRTC'
rtcrtpsender:
count:
type: counter
description: >
The number of RTCRtpSenders created.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_sensitivity:
- technical
notification_emails:
- webrtc-telemetry-alerts@mozilla.com
expires: 116
count_setparameters_compat:
type: counter
description: >
The number of RTCRtpSenders created that use the compatibility mode for
setParameters.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_sensitivity:
- technical
notification_emails:
- webrtc-telemetry-alerts@mozilla.com
expires: 116
used_sendencodings:
type: rate
description: >
The proportion of RTCRtpSenders that were created by an addTransceivers
call that was passed a sendEncodings.
denominator_metric: rtcrtpsender.count
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_sensitivity:
- technical
notification_emails:
- webrtc-telemetry-alerts@mozilla.com
expires: 116
rtcrtpsender.setparameters:
warn_no_getparameters:
type: rate
description: >
The proportion of RTCRtpSenders configured with the setParameters compat
mode that have warned at least once about a setParameters call because
[[LastReturnedParameters]] was not set. (ie; there was not a recent
enough call to getParameters)
denominator_metric: rtcrtpsender.count_setparameters_compat
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_sensitivity:
- technical
notification_emails:
- webrtc-telemetry-alerts@mozilla.com
expires: 116
warn_length_changed:
type: rate
description: >
The proportion of RTCRtpSenders configured with the setParameters compat
mode that have warned at least once about a setParameters call that
attempted to change the number of encodings.
denominator_metric: rtcrtpsender.count_setparameters_compat
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_sensitivity:
- technical
notification_emails:
- webrtc-telemetry-alerts@mozilla.com
expires: 116
warn_rid_changed:
type: rate
description: >
The proportion of RTCRtpSenders configured with the setParameters compat
mode that have warned at least once about a setParameters call that
attempted to change the rid on an encoding (note that we only check this
if the encoding count did not change, see warn_length_changed).
denominator_metric: rtcrtpsender.count_setparameters_compat
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_sensitivity:
- technical
notification_emails:
- webrtc-telemetry-alerts@mozilla.com
expires: 116
warn_no_transactionid:
type: rate
description: >
The proportion of RTCRtpSenders configured with the setParameters compat
mode that have warned at least once about a setParameters call that did
not set the transactionId field.
denominator_metric: rtcrtpsender.count_setparameters_compat
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_sensitivity:
- technical
notification_emails:
- webrtc-telemetry-alerts@mozilla.com
expires: 116
warn_stale_transactionid:
type: rate
description: >
The proportion of RTCRtpSenders configured with the setParameters compat
mode that have warned at least once about a setParameters call that used
a stale transaction id.
denominator_metric: rtcrtpsender.count_setparameters_compat
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_sensitivity:
- technical
notification_emails:
- webrtc-telemetry-alerts@mozilla.com
expires: 116
fail_length_changed:
type: rate
description: >
The proportion of RTCRtpSenders that have thrown an error at least once
about a setParameters call that attempted to change the number of
encodings.
denominator_metric: rtcrtpsender.count
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_sensitivity:
- technical
notification_emails:
- webrtc-telemetry-alerts@mozilla.com
expires: 116
fail_rid_changed:
type: rate
description: >
The proportion of RTCRtpSenders that have thrown an error at least once
about a setParameters call that attempted to change the rid on an
encoding (note that we only check this if the encoding count did not
change, see fail_length_changed).
denominator_metric: rtcrtpsender.count
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_sensitivity:
- technical
notification_emails:
- webrtc-telemetry-alerts@mozilla.com
expires: 116
fail_no_getparameters:
type: rate
description: >
The proportion of RTCRtpSenders that have thrown an error at least once
about a setParameters call because [[LastReturnedParameters]] was not set.
(ie; there was not a recent enough call to getParameters)
denominator_metric: rtcrtpsender.count
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_sensitivity:
- technical
notification_emails:
- webrtc-telemetry-alerts@mozilla.com
expires: 116
fail_no_transactionid:
type: rate
description: >
The proportion of RTCRtpSenders that have thrown an error at least once
about a setParameters call that did not set the transactionId field.
denominator_metric: rtcrtpsender.count
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_sensitivity:
- technical
notification_emails:
- webrtc-telemetry-alerts@mozilla.com
expires: 116
fail_stale_transactionid:
type: rate
description: >
The proportion of RTCRtpSenders that have thrown an error at least once
about a setParameters call that used a stale transaction id.
denominator_metric: rtcrtpsender.count
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_sensitivity:
- technical
notification_emails:
- webrtc-telemetry-alerts@mozilla.com
expires: 116
fail_no_encodings:
type: rate
description: >
The proportion of RTCRtpSenders configured with the setParameters compat
mode that have thrown an error at least once about a setParameters call
that had no encodings (we do not measure this against the general
population of RTCRtpSenders, since without the compat mode this failure
is never observed, because it fails the length change check).
denominator_metric: rtcrtpsender.count_setparameters_compat
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_sensitivity:
- technical
notification_emails:
- webrtc-telemetry-alerts@mozilla.com
expires: 116
fail_other:
type: rate
description: >
The proportion of RTCRtpSenders that have thrown an error at least once
about a setParameters call that had no encodings.
denominator_metric: rtcrtpsender.count
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
data_sensitivity:
- technical
notification_emails:
- webrtc-telemetry-alerts@mozilla.com
expires: 116

View File

@ -7,7 +7,6 @@
#include "sdp/SdpAttribute.h"
#include "sdp/SdpHelper.h"
#include <iomanip>
#include <bitset>
#ifdef CRLF
# undef CRLF
@ -926,7 +925,7 @@ void SdpRidAttributeList::Rid::SerializeParameters(std::ostream& os) const {
// Remove this function. See Bug 1469702
bool SdpRidAttributeList::Rid::Parse(std::istream& is, std::string* error) {
id = ParseToken(is, " ", error);
if (!CheckRidValidity(id, error)) {
if (id.empty()) {
return false;
}
@ -944,65 +943,6 @@ bool SdpRidAttributeList::Rid::Parse(std::istream& is, std::string* error) {
return ParseParameters(is, error);
}
static std::bitset<256> GetAllowedRidCharacters() {
// From RFC 8851:
// rid-id = 1*(alpha-numeric / "-" / "_")
std::bitset<256> result;
for (unsigned char c = 'a'; c <= 'z'; ++c) {
result.set(c);
}
for (unsigned char c = 'A'; c <= 'Z'; ++c) {
result.set(c);
}
for (unsigned char c = '0'; c <= '9'; ++c) {
result.set(c);
}
// NOTE: RFC 8851 says these are allowed, but RFC 8852 says they are not
// https://www.rfc-editor.org/errata/eid7132
// result.set('-');
// result.set('_');
return result;
}
/* static */
bool SdpRidAttributeList::CheckRidValidity(const std::string& aRid,
std::string* aError) {
if (aRid.empty()) {
*aError = "Rid must be non-empty (according to RFC 8851)";
return false;
}
// We need to check against a maximum length, but that is nowhere
// specified in webrtc-pc right now.
if (aRid.size() > 255) {
*aError = "Rid can be at most 255 characters long (according to RFC 8852)";
return false;
}
if (aRid.size() > kMaxRidLength) {
std::ostringstream ss;
ss << "Rid can be at most " << kMaxRidLength
<< " characters long (due to internal limitations)";
*aError = ss.str();
return false;
}
static const std::bitset<256> allowed = GetAllowedRidCharacters();
for (unsigned char c : aRid) {
if (!allowed[c]) {
*aError =
"Rid can contain only alphanumeric characters (according to RFC "
"8852)";
return false;
}
}
return true;
}
// This can be overridden if necessary
size_t SdpRidAttributeList::kMaxRidLength = 255;
void SdpRidAttributeList::Rid::Serialize(std::ostream& os) const {
os << id << " " << direction;
SerializeParameters(os);
@ -1156,9 +1096,6 @@ bool SdpSimulcastAttribute::Version::Parse(std::istream& is,
*error = "Missing rid";
return false;
}
if (!SdpRidAttributeList::CheckRidValidity(value, error)) {
return false;
}
choices.push_back(Encoding(value, paused));
} while (SkipChar(is, ',', error));

View File

@ -952,9 +952,6 @@ class SdpRidAttributeList : public SdpAttribute {
return new SdpRidAttributeList(*this);
}
static bool CheckRidValidity(const std::string& aRid, std::string* aError);
static size_t kMaxRidLength;
virtual void Serialize(std::ostream& os) const override;
// Remove this function. See Bug 1469702

View File

@ -991,38 +991,6 @@ const getTurnHostname = turnUrl => {
return hostAndMaybePort.split(":")[0];
};
// Yo dawg I heard you like Proxies
// Example: let value = await GleanTest.category.metric.testGetValue();
const GleanTest = new Proxy(
{},
{
get(target, categoryName, receiver) {
return new Proxy(
{},
{
get(target, metricName, receiver) {
return {
// The only API we actually implement right now.
async testGetValue() {
return SpecialPowers.spawnChrome(
[categoryName, metricName],
async (categoryName, metricName) => {
await Services.fog.testFlushAllChildren();
const window = this.browsingContext.topChromeWindow;
return window.Glean[categoryName][
metricName
].testGetValue();
}
);
},
};
},
}
);
},
}
);
/**
* This class executes a series of functions in a continuous sequence.
* Promise-bearing functions are executed after the previous promise completes.

View File

@ -140,7 +140,6 @@ scheme=http
[test_peerConnection_closeDuringIce.html]
[test_peerConnection_constructedStream.html]
[test_peerConnection_disabledVideoPreNegotiation.html]
[test_peerConnection_encodingsNegotiation.html]
[test_peerConnection_errorCallbacks.html]
scheme=http
[test_peerConnection_iceFailure.html]
@ -280,21 +279,3 @@ scheme=http
fail-if = 1
[test_peerConnection_telephoneEventFirst.html]
[test_peerConnection_rtcp_rsize.html]
[test_peerConnection_scaleResolution_oldSetParameters.html]
[test_peerConnection_setParameters_maxFramerate_oldSetParameters.html]
[test_peerConnection_setParameters_oldSetParameters.html]
[test_peerConnection_setParameters_scaleResolutionDownBy_oldSetParameters.html]
skip-if = (os == 'win' && processor == 'aarch64') # aarch64 due to bug 1537567
[test_peerConnection_simulcastAnswer_lowResFirst_oldSetParameters.html]
skip-if = toolkit == 'android' # no simulcast support on android
[test_peerConnection_simulcastAnswer_oldSetParameters.html]
skip-if = toolkit == 'android' # no simulcast support on android
[test_peerConnection_simulcastOddResolution_oldSetParameters.html]
skip-if = toolkit == 'android' # no simulcast support on android
[test_peerConnection_simulcastOffer_lowResFirst_oldSetParameters.html]
skip-if = toolkit == 'android' # no simulcast support on android
[test_peerConnection_simulcastOffer_oldSetParameters.html]
skip-if = toolkit == 'android' # no simulcast support on android
[test_peerConnection_glean.html]

View File

@ -8,46 +8,39 @@
* m-lines and tracks.
*/
// Borrowed from wpt, with some dependencies removed.
// Adapted from wpt to improve handling of cases where answerer is the
// simulcast sender, better handling of a=setup and direction attributes, and
// some simplification. Will probably end up merging back at some point.
const ridExtensions = [
"urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id",
"urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id",
];
function ridToMid(description, rids) {
const sections = SDPUtils.splitSections(description.sdp);
function ridToMid(sdpString) {
const sections = SDPUtils.splitSections(sdpString);
const dtls = SDPUtils.getDtlsParameters(sections[1], sections[0]);
const ice = SDPUtils.getIceParameters(sections[1], sections[0]);
const rtpParameters = SDPUtils.parseRtpParameters(sections[1]);
const setupValue = description.sdp.match(/a=setup:(.*)/)[1];
const setupValue = sdpString.match(/a=setup:(.*)/)[1];
const directionValue =
description.sdp.match(/a=sendrecv|a=sendonly|a=recvonly|a=inactive/) ||
sdpString.match(/a=sendrecv|a=sendonly|a=recvonly|a=inactive/) ||
"a=sendrecv";
const mline = SDPUtils.parseMLine(sections[1]);
// Skip mid extension; we are replacing it with the rid extmap
rtpParameters.headerExtensions = rtpParameters.headerExtensions.filter(
ext => ext.uri != "urn:ietf:params:rtp-hdrext:sdes:mid"
ext => {
return ext.uri != "urn:ietf:params:rtp-hdrext:sdes:mid";
}
);
for (const ext of rtpParameters.headerExtensions) {
rtpParameters.headerExtensions.forEach(ext => {
if (ext.uri == "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id") {
ext.uri = "urn:ietf:params:rtp-hdrext:sdes:mid";
}
}
});
// Filter rtx as we have no way to (re)interpret rrid.
// Not doing this makes probing use RTX, it's not understood and ramp-up is slower.
rtpParameters.codecs = rtpParameters.codecs.filter(
c => c.name.toUpperCase() !== "RTX"
);
if (!rids) {
rids = Array.from(description.sdp.matchAll(/a=rid:(.*) send/g)).map(
r => r[1]
);
}
let rids = Array.from(sdpString.matchAll(/a=rid:(.*) send/g)).map(r => r[1]);
let sdp =
SDPUtils.writeSessionBoilerplate() +
@ -57,10 +50,10 @@ function ridToMid(description, rids) {
rids.join(" ") +
"\r\n";
const baseRtpDescription = SDPUtils.writeRtpDescription(
mline.kind,
"video",
rtpParameters
);
for (const rid of rids) {
rids.forEach(rid => {
sdp +=
baseRtpDescription +
"a=mid:" +
@ -72,41 +65,37 @@ function ridToMid(description, rids) {
rid +
"\r\n";
sdp += directionValue + "\r\n";
}
});
return sdp;
}
function midToRid(description, localDescription, rids) {
const sections = SDPUtils.splitSections(description.sdp);
function midToRid(sdpString) {
const sections = SDPUtils.splitSections(sdpString);
const dtls = SDPUtils.getDtlsParameters(sections[1], sections[0]);
const ice = SDPUtils.getIceParameters(sections[1], sections[0]);
const rtpParameters = SDPUtils.parseRtpParameters(sections[1]);
const setupValue = description.sdp.match(/a=setup:(.*)/)[1];
const setupValue = sdpString.match(/a=setup:(.*)/)[1];
const directionValue =
description.sdp.match(/a=sendrecv|a=sendonly|a=recvonly|a=inactive/) ||
sdpString.match(/a=sendrecv|a=sendonly|a=recvonly|a=inactive/) ||
"a=sendrecv";
const mline = SDPUtils.parseMLine(sections[1]);
// Skip rid extensions; we are replacing them with the mid extmap
rtpParameters.headerExtensions = rtpParameters.headerExtensions.filter(
ext => !ridExtensions.includes(ext.uri)
ext => {
return !ridExtensions.includes(ext.uri);
}
);
for (const ext of rtpParameters.headerExtensions) {
rtpParameters.headerExtensions.forEach(ext => {
if (ext.uri == "urn:ietf:params:rtp-hdrext:sdes:mid") {
ext.uri = "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id";
}
}
});
const localMid = localDescription
? SDPUtils.getMid(SDPUtils.splitSections(localDescription.sdp)[1])
: "0";
if (!rids) {
rids = [];
for (let i = 1; i < sections.length; i++) {
rids.push(SDPUtils.getMid(sections[i]));
}
let mids = [];
for (let i = 1; i < sections.length; i++) {
mids.push(SDPUtils.getMid(sections[i]));
}
let sdp =
@ -114,103 +103,22 @@ function midToRid(description, localDescription, rids) {
SDPUtils.writeDtlsParameters(dtls, setupValue) +
SDPUtils.writeIceParameters(ice) +
"a=group:BUNDLE " +
localMid +
mids[0] +
"\r\n";
sdp += SDPUtils.writeRtpDescription(mline.kind, rtpParameters);
sdp += SDPUtils.writeRtpDescription("video", rtpParameters);
// Although we are converting mids to rids, we still need a mid.
// The first one will be consistent with trickle ICE candidates.
sdp += "a=mid:" + localMid + "\r\n";
sdp += "a=mid:" + mids[0] + "\r\n";
sdp += directionValue + "\r\n";
for (const rid of rids) {
const stringrid = String(rid); // allow integers
const choices = stringrid.split(",");
choices.forEach(choice => {
sdp += "a=rid:" + choice + " recv\r\n";
});
}
if (rids.length) {
sdp += "a=simulcast:recv " + rids.join(";") + "\r\n";
}
mids.forEach(mid => {
sdp += "a=rid:" + mid + " recv\r\n";
});
sdp += "a=simulcast:recv " + mids.join(";") + "\r\n";
return sdp;
}
async function doOfferToSendSimulcast(offerer, answerer) {
await offerer.setLocalDescription();
// Is this a renegotiation? If so, we cannot remove (or reorder!) any mids,
// even if some rids have been removed or reordered.
let mids = [];
if (answerer.localDescription) {
// Renegotiation. Mids must be the same as before, because renegotiation
// can never remove or reorder mids, nor can it expand the simulcast
// envelope.
mids = [...answerer.localDescription.sdp.matchAll(/a=mid:(.*)/g)].map(
e => e[1]
);
} else {
// First negotiation; the mids will be exactly the same as the rids
const simulcastAttr = offerer.localDescription.sdp.match(
/a=simulcast:send (.*)/
);
if (simulcastAttr) {
mids = simulcastAttr[1].split(";");
}
}
const nonSimulcastOffer = ridToMid(offerer.localDescription, mids);
await answerer.setRemoteDescription({
type: "offer",
sdp: nonSimulcastOffer,
});
}
async function doAnswerToRecvSimulcast(offerer, answerer, rids) {
await answerer.setLocalDescription();
const simulcastAnswer = midToRid(
answerer.localDescription,
offerer.localDescription,
rids
);
await offerer.setRemoteDescription({ type: "answer", sdp: simulcastAnswer });
}
async function doOfferToRecvSimulcast(offerer, answerer, rids) {
await offerer.setLocalDescription();
const simulcastOffer = midToRid(
offerer.localDescription,
answerer.localDescription,
rids
);
await answerer.setRemoteDescription({ type: "offer", sdp: simulcastOffer });
}
async function doAnswerToSendSimulcast(offerer, answerer) {
await answerer.setLocalDescription();
// See which mids the offerer had; it will barf if we remove or reorder them
const mids = [...offerer.localDescription.sdp.matchAll(/a=mid:(.*)/g)].map(
e => e[1]
);
const nonSimulcastAnswer = ridToMid(answerer.localDescription, mids);
await offerer.setRemoteDescription({
type: "answer",
sdp: nonSimulcastAnswer,
});
}
async function doOfferToSendSimulcastAndAnswer(offerer, answerer, rids) {
await doOfferToSendSimulcast(offerer, answerer);
await doAnswerToRecvSimulcast(offerer, answerer, rids);
}
async function doOfferToRecvSimulcastAndAnswer(offerer, answerer, rids) {
await doOfferToRecvSimulcast(offerer, answerer, rids);
await doAnswerToSendSimulcast(offerer, answerer);
}
// This would be useful for cases other than simulcast, but we do not use it
// anywhere else right now, nor do we have a place for wpt-friendly helpers at
// the moment.

View File

@ -1,85 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<script type="application/javascript" src="pc.js"></script>
<script type="application/javascript" src="simulcast.js"></script>
<script type="application/javascript" src="helpers_from_wpt/sdp.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript">
createHTML({
bug: "1401592",
title: "Simulcast negotiation tests",
visible: true
});
// simulcast negotiation is mostly tested in wpt, but we test a few
// implementation-specific things here.
const tests = [
async function checkVideoEncodingLimit() {
const pc1 = new RTCPeerConnection();
const pc2 = new RTCPeerConnection();
const stream = await navigator.mediaDevices.getUserMedia({video: true});
const sender = pc1.addTrack(stream.getTracks()[0]);
pc2.addTrack(stream.getTracks()[0]);
await doOfferToRecvSimulcast(pc2, pc1, ["1", "2", "3", "4"]);
const {encodings} = sender.getParameters();
const rids = encodings.map(({rid}) => rid);
isDeeply(rids, ["1", "2", "3"]);
pc1.close();
pc2.close();
stream.getTracks().forEach(track => track.stop());
},
// wpt currently does not assume support for 3 encodings, which limits the
// effectiveness of its powers-of-2 test (since it can test only for 1 and 2)
async function checkScaleResolutionDownByAutoFillPowersOf2() {
const pc1 = new RTCPeerConnection();
const pc2 = new RTCPeerConnection();
const stream = await navigator.mediaDevices.getUserMedia({video: true});
const sender = pc1.addTrack(stream.getTracks()[0]);
pc2.addTrack(stream.getTracks()[0]);
await doOfferToRecvSimulcast(pc2, pc1, ["1", "2", "3"]);
const {encodings} = sender.getParameters();
const scaleValues = encodings.map(({scaleResolutionDownBy}) => scaleResolutionDownBy);
isDeeply(scaleValues, [4, 2, 1]);
},
async function checkLibwebrtcRidLengthLimit() {
const pc1 = new RTCPeerConnection();
const pc2 = new RTCPeerConnection();
const stream = await navigator.mediaDevices.getUserMedia({video: true});
const sender = pc1.addTrack(stream.getTracks()[0]);
pc2.addTrack(stream.getTracks()[0]);
await doOfferToRecvSimulcast(pc2, pc1, ["foo", "wibblywobblyjeremybearimy"]);
const {encodings} = sender.getParameters();
const rids = encodings.map(({rid}) => rid);
isDeeply(rids, ["foo"]);
pc1.close();
pc2.close();
stream.getTracks().forEach(track => track.stop());
},
];
runNetworkTest(async () => {
for (const test of tests) {
info(`Running test: ${test.name}`);
await test();
info(`Done running test: ${test.name}`);
}
});
</script>
</pre>
</body>
</html>

View File

@ -1,370 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<script type="application/javascript" src="pc.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript">
createHTML({
bug: "1401592",
title: "Test that glean is recording stats as expected",
visible: true
});
const tests = [
async function checkRTCRtpSenderCount() {
const pc = new RTCPeerConnection();
const oldCount = await GleanTest.rtcrtpsender.count.testGetValue() ?? 0;
const {sender} = pc.addTransceiver('video', {
sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
});
const countDiff = await GleanTest.rtcrtpsender.count.testGetValue() - oldCount;
is(countDiff, 1, "Glean should have recorded the creation of a single RTCRtpSender");
},
async function checkRTCRtpSenderSetParametersCompatCount() {
await pushPrefs(
["media.peerconnection.allow_old_setParameters", true]);
const pc = new RTCPeerConnection();
const oldCount = await GleanTest.rtcrtpsender.countSetparametersCompat.testGetValue() ?? 0;
const {sender} = pc.addTransceiver('video', {
sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
});
const countDiff = await GleanTest.rtcrtpsender.countSetparametersCompat.testGetValue() - oldCount;
is(countDiff, 1, "Glean should have recorded the creation of a single RTCRtpSender that uses the setParameters compat mode");
},
async function checkSendEncodings() {
const pc = new RTCPeerConnection();
const oldRate = await GleanTest.rtcrtpsender.usedSendencodings.testGetValue();
const {sender} = pc.addTransceiver('video', {
sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
});
const newRate = await GleanTest.rtcrtpsender.usedSendencodings.testGetValue();
is(newRate.denominator, oldRate.denominator + 1, "Glean should have recorded the creation of a single RTCRtpSender");
is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded the use of sendEncodings");
},
async function checkAddTransceiverNoSendEncodings() {
const pc = new RTCPeerConnection();
const oldRate = await GleanTest.rtcrtpsender.usedSendencodings.testGetValue();
const {sender} = pc.addTransceiver('video');
const newRate = await GleanTest.rtcrtpsender.usedSendencodings.testGetValue();
is(newRate.denominator, oldRate.denominator + 1, "Glean should have recorded the creation of a single RTCRtpSender");
is(newRate.numerator, oldRate.numerator, "Glean should not have recorded a use of sendEncodings");
},
async function checkAddTrack() {
const pc = new RTCPeerConnection();
const oldRate = await GleanTest.rtcrtpsender.usedSendencodings.testGetValue();
const stream = await navigator.mediaDevices.getUserMedia({video: true});
const sender = pc.addTrack(stream.getTracks()[0]);
const newRate = await GleanTest.rtcrtpsender.usedSendencodings.testGetValue();
is(newRate.denominator, oldRate.denominator + 1, "Glean should have recorded the creation of a single RTCRtpSender");
is(newRate.numerator, oldRate.numerator, "Glean should not have recorded a use of sendEncodings");
},
async function checkBadSetParametersNoGetParametersWarning() {
await pushPrefs(
["media.peerconnection.allow_old_setParameters", true]);
const pc = new RTCPeerConnection();
const {sender} = pc.addTransceiver('video', {
sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
});
let oldRate = await GleanTest.rtcrtpsenderSetparameters.warnNoGetparameters.testGetValue();
await sender.setParameters({encodings: [{rid: "0"},{rid: "1"},{rid: "2"}]});
let newRate = await GleanTest.rtcrtpsenderSetparameters.warnNoGetparameters.testGetValue();
is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded a warning in setParameters due to lack of a getParameters call");
// Glean should only record the warning once per sender!
oldRate = newRate;
await sender.setParameters({encodings: [{rid: "0"},{rid: "1"},{rid: "2"}]});
newRate = await GleanTest.rtcrtpsenderSetparameters.warnNoGetparameters.testGetValue();
is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another warning in setParameters due to lack of a getParameters call");
},
async function checkBadSetParametersLengthChangedWarning() {
await pushPrefs(
["media.peerconnection.allow_old_setParameters", true]);
const pc = new RTCPeerConnection();
const {sender} = pc.addTransceiver('video', {
sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
});
let oldRate = await GleanTest.rtcrtpsenderSetparameters.warnLengthChanged.testGetValue();
let params = sender.getParameters();
params.encodings.pop();
await sender.setParameters(params);
let newRate = await GleanTest.rtcrtpsenderSetparameters.warnLengthChanged.testGetValue();
is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded a warning due to a length change in encodings");
// Glean should only record the warning once per sender!
params = sender.getParameters();
params.encodings.pop();
oldRate = newRate;
await sender.setParameters(params);
newRate = await GleanTest.rtcrtpsenderSetparameters.warnLengthChanged.testGetValue();
is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another warning due to a length change in encodings");
},
async function checkBadSetParametersRidChangedWarning() {
await pushPrefs(
["media.peerconnection.allow_old_setParameters", true]);
const pc = new RTCPeerConnection();
const {sender} = pc.addTransceiver('video', {
sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
});
let oldRate = await GleanTest.rtcrtpsenderSetparameters.warnRidChanged.testGetValue();
let params = sender.getParameters();
params.encodings[1].rid = "foo";
await sender.setParameters(params);
let newRate = await GleanTest.rtcrtpsenderSetparameters.warnRidChanged.testGetValue();
is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded a warning due to a rid change in encodings");
// Glean should only record the warning once per sender!
params = sender.getParameters();
params.encodings[1].rid = "bar";
oldRate = newRate;
await sender.setParameters(params);
newRate = await GleanTest.rtcrtpsenderSetparameters.warnRidChanged.testGetValue();
is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another warning due to a rid change in encodings");
},
async function checkBadSetParametersNoTransactionIdWarning() {
await pushPrefs(
["media.peerconnection.allow_old_setParameters", true]);
const pc = new RTCPeerConnection();
const {sender} = pc.addTransceiver('video', {
sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
});
let oldRate = await GleanTest.rtcrtpsenderSetparameters.warnNoTransactionid.testGetValue();
await sender.setParameters({encodings: [{rid: "0"},{rid: "1"},{rid: "2"}]});
let newRate = await GleanTest.rtcrtpsenderSetparameters.warnNoTransactionid.testGetValue();
is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded a warning due to missing transactionId in setParameters");
// Glean should only record the warning once per sender!
oldRate = newRate;
await sender.setParameters({encodings: [{rid: "0"},{rid: "1"},{rid: "2"}]});
newRate = await GleanTest.rtcrtpsenderSetparameters.warnNoTransactionid.testGetValue();
is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another warning due to missing transactionId in setParameters");
},
async function checkBadSetParametersStaleTransactionIdWarning() {
await pushPrefs(
["media.peerconnection.allow_old_setParameters", true]);
const pc = new RTCPeerConnection();
const {sender} = pc.addTransceiver('video', {
sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
});
let oldRate = await GleanTest.rtcrtpsenderSetparameters.warnStaleTransactionid.testGetValue();
let params = sender.getParameters();
// Cause transactionId to be stale
await pc.createOffer();
// ...but make sure there is a recent getParameters call
sender.getParameters();
await sender.setParameters(params);
let newRate = await GleanTest.rtcrtpsenderSetparameters.warnStaleTransactionid.testGetValue();
is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded a warning due to stale transactionId in setParameters");
// Glean should only record the warning once per sender!
oldRate = newRate;
params = sender.getParameters();
// Cause transactionId to be stale
await pc.createOffer();
// ...but make sure there is a recent getParameters call
sender.getParameters();
await sender.setParameters(params);
newRate = await GleanTest.rtcrtpsenderSetparameters.warnStaleTransactionid.testGetValue();
is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another warning due to stale transactionId in setParameters");
},
async function checkBadSetParametersLengthChangedError() {
await pushPrefs(
["media.peerconnection.allow_old_setParameters", false]);
const pc = new RTCPeerConnection();
const {sender} = pc.addTransceiver('video', {
sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
});
let oldRate = await GleanTest.rtcrtpsenderSetparameters.failLengthChanged.testGetValue();
let params = sender.getParameters();
params.encodings.pop();
try {
await sender.setParameters(params);
} catch(e) {
}
let newRate = await GleanTest.rtcrtpsenderSetparameters.failLengthChanged.testGetValue();
is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded an error due to a length change in encodings");
// Glean should only record the error once per sender!
params = sender.getParameters();
params.encodings.pop();
oldRate = newRate;
try {
await sender.setParameters(params);
} catch (e) {
}
newRate = await GleanTest.rtcrtpsenderSetparameters.failLengthChanged.testGetValue();
is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another error due to a length change in encodings");
},
async function checkBadSetParametersRidChangedError() {
await pushPrefs(
["media.peerconnection.allow_old_setParameters", false]);
const pc = new RTCPeerConnection();
const {sender} = pc.addTransceiver('video', {
sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
});
let oldRate = await GleanTest.rtcrtpsenderSetparameters.failRidChanged.testGetValue();
let params = sender.getParameters();
params.encodings[1].rid = "foo";
try {
await sender.setParameters(params);
} catch (e) {
}
let newRate = await GleanTest.rtcrtpsenderSetparameters.failRidChanged.testGetValue();
is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded an error due to a rid change in encodings");
// Glean should only record the error once per sender!
params = sender.getParameters();
params.encodings[1].rid = "bar";
oldRate = newRate;
try {
await sender.setParameters(params);
} catch (e) {
}
newRate = await GleanTest.rtcrtpsenderSetparameters.failRidChanged.testGetValue();
is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another error due to a rid change in encodings");
},
async function checkBadSetParametersNoGetParametersError() {
await pushPrefs(
["media.peerconnection.allow_old_setParameters", false]);
const pc = new RTCPeerConnection();
const {sender} = pc.addTransceiver('video', {
sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
});
let oldRate = await GleanTest.rtcrtpsenderSetparameters.failNoGetparameters.testGetValue();
try {
await sender.setParameters({encodings: [{rid: "0"},{rid: "1"},{rid: "2"}]});
} catch (e) {
}
let newRate = await GleanTest.rtcrtpsenderSetparameters.failNoGetparameters.testGetValue();
is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded an error in setParameters due to lack of a getParameters call");
// Glean should only record the error once per sender!
oldRate = newRate;
try {
await sender.setParameters({encodings: [{rid: "0"},{rid: "1"},{rid: "2"}]});
} catch (e) {
}
newRate = await GleanTest.rtcrtpsenderSetparameters.failNoGetparameters.testGetValue();
is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another error in setParameters due to lack of a getParameters call");
},
async function checkBadSetParametersStaleTransactionIdError() {
await pushPrefs(
["media.peerconnection.allow_old_setParameters", false]);
const pc = new RTCPeerConnection();
const {sender} = pc.addTransceiver('video', {
sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
});
let oldRate = await GleanTest.rtcrtpsenderSetparameters.failStaleTransactionid.testGetValue();
let params = sender.getParameters();
// Cause transactionId to be stale
await pc.createOffer();
// ...but make sure there is a recent getParameters call
sender.getParameters();
try {
await sender.setParameters(params);
} catch (e) {
}
let newRate = await GleanTest.rtcrtpsenderSetparameters.failStaleTransactionid.testGetValue();
is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded an error due to stale transactionId in setParameters");
// Glean should only record the error once per sender!
oldRate = newRate;
params = sender.getParameters();
// Cause transactionId to be stale
await pc.createOffer();
// ...but make sure there is a recent getParameters call
sender.getParameters();
try {
await sender.setParameters(params);
} catch (e) {
}
newRate = await GleanTest.rtcrtpsenderSetparameters.failStaleTransactionid.testGetValue();
is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another error due to stale transactionId in setParameters");
},
async function checkBadSetParametersNoEncodingsError() {
// If we do not allow the old setParameters, this will fail the length check
// instead.
await pushPrefs(
["media.peerconnection.allow_old_setParameters", true]);
const pc = new RTCPeerConnection();
const {sender} = pc.addTransceiver('video', {
sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
});
let oldRate = await GleanTest.rtcrtpsenderSetparameters.failNoEncodings.testGetValue();
let params = sender.getParameters();
params.encodings = [];
try {
await sender.setParameters(params);
} catch (e) {
}
let newRate = await GleanTest.rtcrtpsenderSetparameters.failNoEncodings.testGetValue();
is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded an error due to stale transactionId in setParameters");
// Glean should only record the error once per sender!
oldRate = newRate;
params = sender.getParameters();
params.encodings = [];
try {
await sender.setParameters(params);
} catch (e) {
}
newRate = await GleanTest.rtcrtpsenderSetparameters.failNoEncodings.testGetValue();
is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another error due to stale transactionId in setParameters");
},
async function checkBadSetParametersOtherError() {
const pc = new RTCPeerConnection();
const {sender} = pc.addTransceiver('video', {
sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
});
let oldRate = await GleanTest.rtcrtpsenderSetparameters.failOther.testGetValue();
let params = sender.getParameters();
params.encodings[0].scaleResolutionDownBy = 0.5;
try {
await sender.setParameters(params);
} catch (e) {
}
let newRate = await GleanTest.rtcrtpsenderSetparameters.failOther.testGetValue();
is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded an error due to some other failure");
// Glean should only record the error once per sender!
oldRate = newRate;
params = sender.getParameters();
params.encodings[0].scaleResolutionDownBy = 0.5;
try {
await sender.setParameters(params);
} catch (e) {
}
newRate = await GleanTest.rtcrtpsenderSetparameters.failOther.testGetValue();
is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another error due to some other failure");
},
];
runNetworkTest(async () => {
for (const test of tests) {
info(`Running test: ${test.name}`);
await test();
info(`Done running test: ${test.name}`);
}
});
</script>
</pre>
</body>
</html>

View File

@ -46,24 +46,15 @@
v1.srcObject = stream;
var sender = pc1.addTrack(stream.getVideoTracks()[0], stream);
let parameters = sender.getParameters();
is(parameters.encodings.length, 1, "Default number of encodings should be 1");
parameters.encodings[0].scaleResolutionDownBy = 0.5;
await mustRejectWith(
"Invalid scaleResolutionDownBy must reject", "RangeError",
() => sender.setParameters(parameters)
() => sender.setParameters(
{ encodings:[{ scaleResolutionDownBy: 0.5 } ] })
);
parameters = sender.getParameters();
parameters.encodings[0].scaleResolutionDownBy = 2;
parameters.encodings[0].maxBitrate = 60000;
await sender.setParameters(parameters);
parameters = sender.getParameters();
is(parameters.encodings[0].scaleResolutionDownBy, 2, "Should be able to set scaleResolutionDownBy");
is(parameters.encodings[0].maxBitrate, 60000, "Should be able to set maxBitrate");
await sender.setParameters({ encodings: [{ maxBitrate: 60000,
scaleResolutionDownBy: 2 }] });
let offer = await pc1.createOffer();
if (codec == "VP8") {

View File

@ -1,101 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<script type="application/javascript" src="pc.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript">
createHTML({
bug: "1244913",
title: "Scale resolution down on a PeerConnection",
visible: true
});
var mustRejectWith = (msg, reason, f) =>
f().then(() => ok(false, msg),
e => is(e.name, reason, msg));
async function testScale(codec) {
var pc1 = new RTCPeerConnection();
var pc2 = new RTCPeerConnection();
var add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed);
pc1.onicecandidate = e => add(pc2, e.candidate, generateErrorCallback());
pc2.onicecandidate = e => add(pc1, e.candidate, generateErrorCallback());
info("testing scaling with " + codec);
let stream = await navigator.mediaDevices.getUserMedia({ video: true });
var v1 = createMediaElement('video', 'v1');
var v2 = createMediaElement('video', 'v2');
var ontrackfired = new Promise(resolve => pc2.ontrack = e => resolve(e));
var v2loadedmetadata = new Promise(resolve => v2.onloadedmetadata = resolve);
is(v2.currentTime, 0, "v2.currentTime is zero at outset");
v1.srcObject = stream;
var sender = pc1.addTrack(stream.getVideoTracks()[0], stream);
const otherErrorStart = await GleanTest.rtcrtpsenderSetparameters.failOther.testGetValue();
const noTransactionIdWarningStart = await GleanTest.rtcrtpsenderSetparameters.warnNoTransactionid.testGetValue();
await mustRejectWith(
"Invalid scaleResolutionDownBy must reject", "RangeError",
() => sender.setParameters(
{ encodings:[{ scaleResolutionDownBy: 0.5 } ] })
);
const otherErrorEnd = await GleanTest.rtcrtpsenderSetparameters.failOther.testGetValue();
const noTransactionIdWarningEnd = await GleanTest.rtcrtpsenderSetparameters.warnNoTransactionid.testGetValue();
// Make sure Glean is recording these statistics
is(otherErrorEnd.denominator, otherErrorStart.denominator, "No new RTCRtpSenders were created during this time");
is(otherErrorEnd.numerator, otherErrorStart.numerator + 1, "RTCRtpSender.setParameters reported a failure via Glean");
is(noTransactionIdWarningEnd.denominator, noTransactionIdWarningStart.denominator, "No new RTCRtpSenders were created during this time");
is(noTransactionIdWarningEnd.numerator, noTransactionIdWarningStart.numerator + 1, "Glean should have recorded a warning due to missing transactionId");
await sender.setParameters({ encodings: [{ maxBitrate: 60000,
scaleResolutionDownBy: 2 }] });
let offer = await pc1.createOffer();
if (codec == "VP8") {
offer.sdp = sdputils.removeAllButPayloadType(offer.sdp, 126);
}
await pc1.setLocalDescription(offer);
await pc2.setRemoteDescription(pc1.localDescription);
let answer = await pc2.createAnswer();
await pc2.setLocalDescription(answer);
await pc1.setRemoteDescription(pc2.localDescription);
let trackevent = await ontrackfired;
v2.srcObject = trackevent.streams[0];
await v2loadedmetadata;
await waitUntil(() => v2.currentTime > 0);
ok(v2.currentTime > 0, "v2.currentTime is moving (" + v2.currentTime + ")");
ok(v1.videoWidth > 0, "source width is positive");
ok(v1.videoHeight > 0, "source height is positive");
is(v2.videoWidth, v1.videoWidth / 2, "sink is half the width of source");
is(v2.videoHeight, v1.videoHeight / 2, "sink is half the height of source");
stream.getTracks().forEach(track => track.stop());
v1.srcObject = v2.srcObject = null;
pc1.close()
pc2.close()
}
runNetworkTest(async () => {
await matchPlatformH264CodecPrefs();
await pushPrefs(['media.peerconnection.video.lock_scaling', true]);
await testScale("VP8");
await testScale("H264");
});
</script>
</pre>
</body>
</html>

View File

@ -12,158 +12,72 @@ createHTML({
visible: true
});
function buildMaximumSendEncodings() {
const sendEncodings = [];
while (true) {
// isDeeply does not see identical string primitives and String objects
// as the same, so we make this a string primitive.
sendEncodings.push({rid: `${sendEncodings.length}`});
const pc = new RTCPeerConnection();
const {sender} = pc.addTransceiver('video', {sendEncodings});
const {encodings} = sender.getParameters();
if (encodings.length < sendEncodings.length) {
sendEncodings.pop();
return sendEncodings;
}
}
function parameterstest(pc) {
ok(pc.getSenders().length, "have senders");
var sender = pc.getSenders()[0];
var testParameters = (params, errorName, errorMsg) => {
info("Trying to set " + JSON.stringify(params));
var validateParameters = (a, b) => {
var validateEncoding = (a, b) => {
is(a.rid, b.rid, "same rid");
is(a.maxBitrate, b.maxBitrate, "same maxBitrate");
is(a.maxFramerate, b.maxFramerate, "same maxFramerate");
is(a.scaleResolutionDownBy, b.scaleResolutionDownBy,
"same scaleResolutionDownBy");
};
is(a.encodings.length, (b.encodings || []).length, "same encodings");
a.encodings.forEach((en, i) => validateEncoding(en, b.encodings[i]));
};
var before = JSON.stringify(sender.getParameters());
isnot(JSON.stringify(params), before, "starting condition");
var p = sender.setParameters(params)
.then(() => {
isnot(JSON.stringify(sender.getParameters()), before, "parameters changed");
validateParameters(sender.getParameters(), params);
is(null, errorName || null, "is success expected");
}, e => {
is(e.name, errorName, "correct error name");
is(e.message, errorMsg, "correct error message");
});
is(JSON.stringify(sender.getParameters()), before, "parameters not set yet");
return p;
};
return [
[{ encodings: [ { rid: "foo", maxBitrate: 40000, scaleResolutionDownBy: 2 },
{ rid: "bar", maxBitrate: 10000, scaleResolutionDownBy: 4 }]
}],
[{ encodings: [{ maxBitrate: 10000, scaleResolutionDownBy: 4 }]}],
[{ encodings: [{ maxFramerate: 0.0, scaleResolutionDownBy: 1 }]}],
[{ encodings: [{ maxFramerate: 30.5, scaleResolutionDownBy: 1 }]}],
[{ encodings: [{ maxFramerate: -1, scaleResolutionDownBy: 1 }]}, "RangeError", "maxFramerate must be non-negative"],
[{ encodings: [{ maxBitrate: 40000 },
{ rid: "bar", maxBitrate: 10000 }] }, "TypeError", "Missing rid"],
[{ encodings: [{ rid: "foo", maxBitrate: 40000 },
{ rid: "bar", maxBitrate: 10000 },
{ rid: "bar", maxBitrate: 20000 }] }, "TypeError", "Duplicate rid"],
[{}]
].reduce((p, args) => p.then(() => testParameters.apply(this, args)),
Promise.resolve());
}
// setParameters is mostly tested in wpt, but we test a few
// implementation-specific things here. Other mochitests check whether the
// set parameters actually have the desired effect on the media streams.
const tests = [
runNetworkTest(() => {
const test = new PeerConnectionTest();
test.setMediaConstraints([{video: true}], [{video: true}]);
test.chain.removeAfter("PC_REMOTE_WAIT_FOR_MEDIA_FLOW");
// wpt currently does not assume support for 3 encodings, which limits the
// effectiveness of its powers-of-2 test (since it can test only for 1 and 2)
async function checkScaleResolutionDownByAutoFillPowersOf2() {
const pc = new RTCPeerConnection();
const {sender} = pc.addTransceiver('video', {
sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
});
const {encodings} = sender.getParameters();
const scaleValues = encodings.map(({scaleResolutionDownBy}) => scaleResolutionDownBy);
isDeeply(scaleValues, [4, 2, 1]);
},
// wpt currently does not assume support for 3 encodings, which limits the
// effectiveness of its fill-with-1 test
async function checkScaleResolutionDownByAutoFillWith1() {
const pc = new RTCPeerConnection();
const {sender} = pc.addTransceiver('video', {
sendEncodings: [
{rid: "0"},{rid: "1", scaleResolutionDownBy: 3},{rid: "2"}
]
});
const {encodings} = sender.getParameters();
const scaleValues = encodings.map(({scaleResolutionDownBy}) => scaleResolutionDownBy);
isDeeply(scaleValues, [1, 3, 1]);
},
async function checkVideoEncodingLimit() {
const pc = new RTCPeerConnection();
const maxSendEncodings = buildMaximumSendEncodings();
const sendEncodings = maxSendEncodings.concat({rid: "a"});
const {sender} = pc.addTransceiver('video', {sendEncodings});
const {encodings} = sender.getParameters();
const rids = encodings.map(({rid}) => rid);
const expectedRids = maxSendEncodings.map(({rid}) => rid);
isDeeply(rids, expectedRids);
const scaleValues = encodings.map(({scaleResolutionDownBy}) => scaleResolutionDownBy);
const expectedScaleValues = [];
let scale = 1;
while (expectedScaleValues.length < maxSendEncodings.length) {
expectedScaleValues.push(scale);
scale *= 2;
// Test sender parameters.
test.chain.append([
function PC_LOCAL_SET_PARAMETERS(test) {
return parameterstest(test.pcLocal._pc);
}
isDeeply(scaleValues, expectedScaleValues.reverse());
},
]);
async function checkScaleDownByInTrimmedEncoding() {
const pc = new RTCPeerConnection();
const maxSendEncodings = buildMaximumSendEncodings();
const sendEncodings = maxSendEncodings.concat({rid: "a", scaleResolutionDownBy: 3});
const {sender} = pc.addTransceiver('video', {sendEncodings});
const {encodings} = sender.getParameters();
const rids = encodings.map(({rid}) => rid);
const expectedRids = maxSendEncodings.map(({rid}) => rid);
isDeeply(rids, expectedRids);
const scaleValues = encodings.map(({scaleResolutionDownBy}) => scaleResolutionDownBy);
const expectedScaleValues = maxSendEncodings.map(() => 1);
isDeeply(scaleValues, expectedScaleValues);
},
async function checkLibwebrtcRidLengthLimit() {
const pc = new RTCPeerConnection();
try {
pc.addTransceiver('video', {
sendEncodings: [{rid: "wibblywobblyjeremybearimy"}]}
);
ok(false, "Rid should be too long for libwebrtc!");
} catch (e) {
is(e.name, "TypeError",
"Rid that is too long for libwebrtc should result in a TypeError");
}
},
async function checkErrorsInTrimmedEncodings() {
const pc = new RTCPeerConnection();
const maxSendEncodings = buildMaximumSendEncodings();
try {
const sendEncodings = maxSendEncodings.concat({rid: "foo-bar"});
pc.addTransceiver('video', { sendEncodings });
ok(false, "Should throw due to invalid rid characters");
} catch (e) {
is(e.name, "TypeError")
}
try {
const sendEncodings = maxSendEncodings.concat({rid: "wibblywobblyjeremybearimy"});
pc.addTransceiver('video', { sendEncodings });
ok(false, "Should throw because rid too long");
} catch (e) {
is(e.name, "TypeError")
}
try {
const sendEncodings = maxSendEncodings.concat({scaleResolutionDownBy: 2});
pc.addTransceiver('video', { sendEncodings });
ok(false, "Should throw due to missing rid");
} catch (e) {
is(e.name, "TypeError")
}
try {
const sendEncodings = maxSendEncodings.concat(maxSendEncodings[0]);
pc.addTransceiver('video', { sendEncodings });
ok(false, "Should throw due to duplicate rid");
} catch (e) {
is(e.name, "TypeError")
}
try {
const sendEncodings = maxSendEncodings.concat({rid: maxSendEncodings.length, scaleResolutionDownBy: 0});
pc.addTransceiver('video', { sendEncodings });
ok(false, "Should throw due to invalid scaleResolutionDownBy");
} catch (e) {
is(e.name, "RangeError")
}
try {
const sendEncodings = maxSendEncodings.concat({rid: maxSendEncodings.length, maxFramerate: -1});
pc.addTransceiver('video', { sendEncodings });
ok(false, "Should throw due to invalid maxFramerate");
} catch (e) {
is(e.name, "RangeError")
}
},
];
runNetworkTest(async () => {
await pushPrefs(
["media.peerconnection.allow_old_setParameters", false]);
for (const test of tests) {
info(`Running test: ${test.name}`);
await test();
info(`Done running test: ${test.name}`);
}
return test.run();
});
</script>

View File

@ -14,16 +14,13 @@ createHTML({
let sender, receiver;
async function checkMaxFrameRate(rate) {
const parameters = sender.getParameters();
parameters.encodings[0].maxFramerate = rate;
await sender.setParameters(parameters);
sender.setParameters({ encodings: [{ maxFramerate: rate }] });
await wait(2000);
const stats = Array.from((await receiver.getStats()).values());
const inboundRtp = stats.find(stat => stat.type == "inbound-rtp");
info(`inbound-rtp stats: ${JSON.stringify(inboundRtp)}`);
const fps = inboundRtp.framesPerSecond;
ok(fps <= (rate * 1.1) + 1,
`fps is an appropriate value (${fps}) for rate (${rate})`);
ok(fps <= (rate * 1.1) + 1, `fps is an appropriate value (${fps}) for rate (${rate})`);
}
runNetworkTest(async function (options) {
@ -32,9 +29,9 @@ runNetworkTest(async function (options) {
test.chain.append([
function CHECK_PRECONDITIONS() {
is(test.pcLocal._pc.getSenders().length, 1,
"Should have 1 local sender");
"Should have 1 local sender");
is(test.pcRemote._pc.getReceivers().length, 1,
"Should have 1 remote receiver");
"Should have 1 remote receiver");
sender = test.pcLocal._pc.getSenders()[0];
receiver = test.pcRemote._pc.getReceivers()[0];

View File

@ -1,60 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<script type="application/javascript" src="pc.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript">
createHTML({
bug: "1611957",
title: "Live-updating maxFramerate"
});
let sender, receiver;
async function checkMaxFrameRate(rate) {
sender.setParameters({ encodings: [{ maxFramerate: rate }] });
await wait(2000);
const stats = Array.from((await receiver.getStats()).values());
const inboundRtp = stats.find(stat => stat.type == "inbound-rtp");
info(`inbound-rtp stats: ${JSON.stringify(inboundRtp)}`);
const fps = inboundRtp.framesPerSecond;
ok(fps <= (rate * 1.1) + 1, `fps is an appropriate value (${fps}) for rate (${rate})`);
}
runNetworkTest(async function (options) {
let test = new PeerConnectionTest(options);
test.setMediaConstraints([{video: true}], []);
test.chain.append([
function CHECK_PRECONDITIONS() {
is(test.pcLocal._pc.getSenders().length, 1,
"Should have 1 local sender");
is(test.pcRemote._pc.getReceivers().length, 1,
"Should have 1 remote receiver");
sender = test.pcLocal._pc.getSenders()[0];
receiver = test.pcRemote._pc.getReceivers()[0];
},
function PC_LOCAL_SET_MAX_FRAMERATE_2() {
return checkMaxFrameRate(2);
},
function PC_LOCAL_SET_MAX_FRAMERATE_4() {
return checkMaxFrameRate(4);
},
function PC_LOCAL_SET_MAX_FRAMERATE_15() {
return checkMaxFrameRate(15);
},
function PC_LOCAL_SET_MAX_FRAMERATE_8() {
return checkMaxFrameRate(8);
},
function PC_LOCAL_SET_MAX_FRAMERATE_1() {
return checkMaxFrameRate(1);
},
]);
await test.run();
});
</script>
</pre>
</body>
</html>

View File

@ -1,86 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<script type="application/javascript" src="pc.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript">
createHTML({
bug: "1230184",
title: "Set parameters on sender",
visible: true
});
function parameterstest(pc) {
ok(pc.getSenders().length, "have senders");
var sender = pc.getSenders()[0];
var testParameters = (params, errorName, errorMsg) => {
info("Trying to set " + JSON.stringify(params));
var validateParameters = (a, b) => {
var validateEncoding = (a, b) => {
is(a.rid, b.rid, "same rid");
is(a.maxBitrate, b.maxBitrate, "same maxBitrate");
is(a.maxFramerate, b.maxFramerate, "same maxFramerate");
is(a.scaleResolutionDownBy, b.scaleResolutionDownBy,
"same scaleResolutionDownBy");
};
is(a.encodings.length, (b.encodings || []).length, "same encodings");
a.encodings.forEach((en, i) => validateEncoding(en, b.encodings[i]));
};
var before = JSON.stringify(sender.getParameters());
isnot(JSON.stringify(params), before, "starting condition");
var p = sender.setParameters(params)
.then(() => {
isnot(JSON.stringify(sender.getParameters()), before, "parameters changed");
validateParameters(sender.getParameters(), params);
is(null, errorName || null, "is success expected");
}, e => {
is(e.name, errorName, "correct error name");
is(e.message, errorMsg, "correct error message");
});
is(JSON.stringify(sender.getParameters()), before, "parameters not set yet");
return p;
};
return [
[{ encodings: [ { rid: "foo", maxBitrate: 40000, scaleResolutionDownBy: 2 },
{ rid: "bar", maxBitrate: 10000, scaleResolutionDownBy: 4 }]
}],
[{ encodings: [{ maxBitrate: 10000, scaleResolutionDownBy: 4 }]}],
[{ encodings: [{ maxFramerate: 0.0, scaleResolutionDownBy: 1 }]}],
[{ encodings: [{ maxFramerate: 30.5, scaleResolutionDownBy: 1 }]}],
[{ encodings: [{ maxFramerate: -1, scaleResolutionDownBy: 1 }]}, "RangeError", "maxFramerate must be non-negative"],
[{ encodings: [{ maxBitrate: 40000 },
{ rid: "bar", maxBitrate: 10000 }] }, "TypeError", "Missing rid"],
[{ encodings: [{ rid: "foo", maxBitrate: 40000 },
{ rid: "bar", maxBitrate: 10000 },
{ rid: "bar", maxBitrate: 20000 }] }, "TypeError", "Duplicate rid"],
[{}, "TypeError", `RTCRtpSender.setParameters: Missing required 'encodings' member of RTCRtpSendParameters.`]
].reduce((p, args) => p.then(() => testParameters.apply(this, args)),
Promise.resolve());
}
runNetworkTest(() => {
const test = new PeerConnectionTest();
test.setMediaConstraints([{video: true}], [{video: true}]);
test.chain.removeAfter("PC_REMOTE_WAIT_FOR_MEDIA_FLOW");
// Test sender parameters.
test.chain.append([
function PC_LOCAL_SET_PARAMETERS(test) {
return parameterstest(test.pcLocal._pc);
}
]);
return test.run();
});
</script>
</pre>
</body>
</html>

View File

@ -22,9 +22,7 @@ let originalWidth, originalHeight;
let resolutionAlignment = 1;
async function checkScaleDownBy(scale) {
const parameters = sender.getParameters();
parameters.encodings[0].scaleResolutionDownBy = scale;
await sender.setParameters(parameters);
sender.setParameters({ encodings: [{ scaleResolutionDownBy: scale }] });
await haveEvent(remoteElem, "resize", wait(5000, new Error("Timeout")));
// Find the expected resolution. Internally we floor the exact scaling, then

View File

@ -1,83 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<script type="application/javascript" src="pc.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript">
createHTML({
bug: "1253499",
title: "Live-updating scaleResolutionDownBy"
});
let sender, localElem, remoteElem;
let originalWidth, originalHeight;
async function checkScaleDownBy(scale) {
sender.setParameters({ encodings: [{ scaleResolutionDownBy: scale }] });
await haveEvent(remoteElem, "resize", wait(5000, new Error("Timeout")));
// Find the expected resolution. Internally we pick the closest lower
// resolution with an identical aspect ratio.
let expectedWidth = Math.floor(originalWidth / scale);
let expectedHeight = Math.floor(originalHeight / scale);
is(remoteElem.videoWidth, expectedWidth,
`Width should have scaled down by ${scale}`);
is(remoteElem.videoHeight, expectedHeight,
`Height should have scaled down by ${scale}`);
}
runNetworkTest(async function (options) {
await pushPrefs(['media.peerconnection.video.lock_scaling', true]);
// [TODO] re-enable HW decoder after bug 1526207 is fixed.
if (navigator.userAgent.includes("Android")) {
await pushPrefs(["media.navigator.mediadatadecoder_vpx_enabled", false],
["media.webrtc.hw.h264.enabled", false]);
}
let test = new PeerConnectionTest(options);
test.setMediaConstraints([{video: true}], []);
test.chain.append([
function CHECK_PRECONDITIONS() {
is(test.pcLocal._pc.getSenders().length, 1,
"Should have 1 local sender");
is(test.pcLocal.localMediaElements.length, 1,
"Should have 1 local sending media element");
is(test.pcRemote.remoteMediaElements.length, 1,
"Should have 1 remote media element");
sender = test.pcLocal._pc.getSenders()[0];
localElem = test.pcLocal.localMediaElements[0];
remoteElem = test.pcRemote.remoteMediaElements[0];
remoteElem.addEventListener("resize", () =>
info(`Video resized to ${remoteElem.videoWidth}x${remoteElem.videoHeight}`));
originalWidth = localElem.videoWidth;
originalHeight = localElem.videoHeight;
info(`Original width is ${originalWidth}`);
},
function PC_LOCAL_SCALEDOWNBY_2() {
return checkScaleDownBy(2);
},
function PC_LOCAL_SCALEDOWNBY_4() {
return checkScaleDownBy(4);
},
function PC_LOCAL_SCALEDOWNBY_15() {
return checkScaleDownBy(15);
},
function PC_LOCAL_SCALEDOWNBY_8() {
return checkScaleDownBy(8);
},
function PC_LOCAL_SCALEDOWNBY_1() {
return checkScaleDownBy(1);
},
]);
await test.run();
});
</script>
</pre>
</body>
</html>

View File

@ -18,6 +18,7 @@
runNetworkTest(async () => {
await pushPrefs(
['media.peerconnection.simulcast', true],
// 180Kbps was determined empirically, set well-higher than
// the 80Kbps+overhead needed for the two simulcast streams.
// 100Kbps was apparently too low.
@ -40,22 +41,18 @@
offerer.addTransceiver('video', { direction: 'recvonly' });
offerer.addTransceiver('video', { direction: 'recvonly' });
const offer = await offerer.createOffer();
const mungedOffer = midToRid(offer);
info(`Transformed recv offer to simulcast: ${offer.sdp} to ${mungedOffer}`);
await answerer.setRemoteDescription({type: 'offer', sdp: mungedOffer});
// One send transceiver, that will be used to send both simulcast streams
const emitter = new VideoFrameEmitter();
const videoStream = emitter.stream();
const sender = answerer.addTrack(videoStream.getVideoTracks()[0], videoStream);
let parameters = sender.getParameters();
is(parameters.encodings.length, 2);
is(answerer.getSenders().length, 1);
answerer.addTrack(videoStream.getVideoTracks()[0], videoStream);
emitter.start();
const offer = await offerer.createOffer();
const mungedOffer = midToRid(offer.sdp);
info(`Transformed recv offer to simulcast: ${offer.sdp} to ${mungedOffer}`);
await answerer.setRemoteDescription({type: 'offer', sdp: mungedOffer});
await offerer.setLocalDescription(offer);
const rids = offerer.getTransceivers().map(t => t.mid);
@ -64,19 +61,17 @@
ok(rids[1] != '', 'Second mid should be non-empty');
info(`rids: ${JSON.stringify(rids)}`);
parameters = sender.getParameters();
info(`parameters: ${JSON.stringify(parameters)}`);
const observedRids = parameters.encodings.map(({rid}) => rid);
isDeeply(observedRids, rids);
parameters.encodings[0].maxBitrate = 40000;
parameters.encodings[0].scaleResolutionDownBy = 1;
parameters.encodings[1].maxBitrate = 40000;
parameters.encodings[1].scaleResolutionDownBy = 2;
await sender.setParameters(parameters);
const sender = answerer.getSenders()[0];
await sender.setParameters({
encodings: [
{ rid: rids[0], maxBitrate: 40000 },
{ rid: rids[1], maxBitrate: 40000, scaleResolutionDownBy: 2 }
]
});
const answer = await answerer.createAnswer();
const mungedAnswer = ridToMid(answer);
const mungedAnswer = ridToMid(answer.sdp);
info(`Transformed send simulcast answer to multiple m-sections: ${answer.sdp} to ${mungedAnswer}`);
await offerer.setRemoteDescription({type: 'answer', sdp: mungedAnswer});
await answerer.setLocalDescription(answer);
@ -104,7 +99,6 @@
"sink is 1/2 height of source, modulo our cropping algorithm");
await statsReady;
info("Stats ready");
const senderStats = await sender.getStats();
checkSenderStats(senderStats, 2);
checkExpectedFields(senderStats);

View File

@ -18,6 +18,7 @@
runNetworkTest(async () => {
await pushPrefs(
['media.peerconnection.simulcast', true],
// 180Kbps was determined empirically, set well-higher than
// the 80Kbps+overhead needed for the two simulcast streams.
// 100Kbps was apparently too low.
@ -48,7 +49,7 @@
const offer = await offerer.createOffer();
const mungedOffer = midToRid(offer);
const mungedOffer = midToRid(offer.sdp);
info(`Transformed recv offer to simulcast: ${offer.sdp} to ${mungedOffer}`);
await answerer.setRemoteDescription({type: 'offer', sdp: mungedOffer});
@ -61,15 +62,16 @@
info(`rids: ${JSON.stringify(rids)}`);
const sender = answerer.getSenders()[0];
const parameters = sender.getParameters();
parameters.encodings[0].maxBitrate = 40000;
parameters.encodings[0].scaleResolutionDownBy = 2;
parameters.encodings[1].maxBitrate = 40000;
await sender.setParameters(parameters);
sender.setParameters({
encodings: [
{ rid: rids[0], maxBitrate: 40000, scaleResolutionDownBy: 2 },
{ rid: rids[1], maxBitrate: 40000 }
]
});
const answer = await answerer.createAnswer();
const mungedAnswer = ridToMid(answer);
const mungedAnswer = ridToMid(answer.sdp);
info(`Transformed send simulcast answer to multiple m-sections: ${answer.sdp} to ${mungedAnswer}`);
await offerer.setRemoteDescription({type: 'answer', sdp: mungedAnswer});
await answerer.setLocalDescription(answer);

View File

@ -1,115 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<script type="application/javascript" src="pc.js"></script>
<script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
<script type="application/javascript" src="helpers_from_wpt/sdp.js"></script>
<script type="application/javascript" src="simulcast.js"></script>
<script type="application/javascript" src="stats.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript">
createHTML({
bug: "1231507",
title: "Basic video-only peer connection with Simulcast answer, first rid has lowest resolution",
visible: true
});
runNetworkTest(async () => {
await pushPrefs(
['media.peerconnection.simulcast', true],
// 180Kbps was determined empirically, set well-higher than
// the 80Kbps+overhead needed for the two simulcast streams.
// 100Kbps was apparently too low.
['media.peerconnection.video.min_bitrate_estimate', 180*1000]);
const offerer = new RTCPeerConnection();
const answerer = new RTCPeerConnection();
const add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed);
offerer.onicecandidate = e => add(answerer, e.candidate, generateErrorCallback());
answerer.onicecandidate = e => add(offerer, e.candidate, generateErrorCallback());
const metadataToBeLoaded = [];
offerer.ontrack = (e) => {
metadataToBeLoaded.push(getPlaybackWithLoadedMetadata(e.track));
};
// Two recv transceivers, one for each simulcast stream
offerer.addTransceiver('video', { direction: 'recvonly' });
offerer.addTransceiver('video', { direction: 'recvonly' });
// One send transceiver, that will be used to send both simulcast streams
const emitter = new VideoFrameEmitter();
const videoStream = emitter.stream();
answerer.addTrack(videoStream.getVideoTracks()[0], videoStream);
emitter.start();
const offer = await offerer.createOffer();
const mungedOffer = midToRid(offer);
info(`Transformed recv offer to simulcast: ${offer.sdp} to ${mungedOffer}`);
await answerer.setRemoteDescription({type: "offer", sdp: mungedOffer});
await offerer.setLocalDescription(offer);
const rids = offerer.getTransceivers().map(t => t.mid);
is(rids.length, 2, 'Should have 2 mids in offer');
ok(rids[0] != '', 'First mid should be non-empty');
ok(rids[1] != '', 'Second mid should be non-empty');
info(`rids: ${JSON.stringify(rids)}`);
const sender = answerer.getSenders()[0];
sender.setParameters({
encodings: [
{ rid: rids[0], maxBitrate: 40000, scaleResolutionDownBy: 2 },
{ rid: rids[1], maxBitrate: 40000 }
]
});
const answer = await answerer.createAnswer();
const mungedAnswer = ridToMid(answer);
info(`Transformed send simulcast answer to multiple m-sections: ${answer.sdp} to ${mungedAnswer}`);
await offerer.setRemoteDescription({type: "answer", sdp: mungedAnswer});
await answerer.setLocalDescription(answer);
is(metadataToBeLoaded.length, 2, 'Offerer should have gotten 2 ontrack events');
info('Waiting for 2 loadedmetadata events');
const videoElems = await Promise.all(metadataToBeLoaded);
const statsReady =
Promise.all([waitForSyncedRtcp(offerer), waitForSyncedRtcp(answerer)]);
const helper = new VideoStreamHelper();
info('Waiting for first video element to start playing');
await helper.checkVideoPlaying(videoElems[0]);
info('Waiting for second video element to start playing');
await helper.checkVideoPlaying(videoElems[1]);
is(videoElems[1].videoWidth, 50,
"sink is same width as source, modulo our cropping algorithm");
is(videoElems[1].videoHeight, 50,
"sink is same height as source, modulo our cropping algorithm");
is(videoElems[0].videoWidth, 25,
"sink is 1/2 width of source, modulo our cropping algorithm");
is(videoElems[0].videoHeight, 25,
"sink is 1/2 height of source, modulo our cropping algorithm");
await statsReady;
const senderStats = await sender.getStats();
checkSenderStats(senderStats, 2);
checkExpectedFields(senderStats);
pedanticChecks(senderStats);
emitter.stop();
videoStream.getVideoTracks()[0].stop();
offerer.close();
answerer.close();
});
</script>
</pre>
</body>
</html>

View File

@ -1,115 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<script type="application/javascript" src="pc.js"></script>
<script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
<script type="application/javascript" src="helpers_from_wpt/sdp.js"></script>
<script type="application/javascript" src="simulcast.js"></script>
<script type="application/javascript" src="stats.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript">
createHTML({
bug: "1231507",
title: "Basic video-only peer connection with Simulcast answer",
visible: true
});
runNetworkTest(async () => {
await pushPrefs(
['media.peerconnection.simulcast', true],
// 180Kbps was determined empirically, set well-higher than
// the 80Kbps+overhead needed for the two simulcast streams.
// 100Kbps was apparently too low.
['media.peerconnection.video.min_bitrate_estimate', 180*1000]);
const offerer = new RTCPeerConnection();
const answerer = new RTCPeerConnection();
const add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed);
offerer.onicecandidate = e => add(answerer, e.candidate, generateErrorCallback());
answerer.onicecandidate = e => add(offerer, e.candidate, generateErrorCallback());
const metadataToBeLoaded = [];
offerer.ontrack = (e) => {
metadataToBeLoaded.push(getPlaybackWithLoadedMetadata(e.track));
};
// Two recv transceivers, one for each simulcast stream
offerer.addTransceiver('video', { direction: 'recvonly' });
offerer.addTransceiver('video', { direction: 'recvonly' });
// One send transceiver, that will be used to send both simulcast streams
const emitter = new VideoFrameEmitter();
const videoStream = emitter.stream();
answerer.addTrack(videoStream.getVideoTracks()[0], videoStream);
emitter.start();
const offer = await offerer.createOffer();
const mungedOffer = midToRid(offer);
info(`Transformed recv offer to simulcast: ${offer.sdp} to ${mungedOffer}`);
await answerer.setRemoteDescription({type: "offer", sdp: mungedOffer});
await offerer.setLocalDescription(offer);
const rids = offerer.getTransceivers().map(t => t.mid);
is(rids.length, 2, 'Should have 2 mids in offer');
ok(rids[0] != '', 'First mid should be non-empty');
ok(rids[1] != '', 'Second mid should be non-empty');
info(`rids: ${JSON.stringify(rids)}`);
const sender = answerer.getSenders()[0];
await sender.setParameters({
encodings: [
{ rid: rids[0], maxBitrate: 40000 },
{ rid: rids[1], maxBitrate: 40000, scaleResolutionDownBy: 2 }
]
});
const answer = await answerer.createAnswer();
const mungedAnswer = ridToMid(answer);
info(`Transformed send simulcast answer to multiple m-sections: ${answer.sdp} to ${mungedAnswer}`);
await offerer.setRemoteDescription({type: "answer", sdp: mungedAnswer});
await answerer.setLocalDescription(answer);
is(metadataToBeLoaded.length, 2, 'Offerer should have gotten 2 ontrack events');
info('Waiting for 2 loadedmetadata events');
const videoElems = await Promise.all(metadataToBeLoaded);
const statsReady =
Promise.all([waitForSyncedRtcp(offerer), waitForSyncedRtcp(answerer)]);
const helper = new VideoStreamHelper();
info('Waiting for first video element to start playing');
await helper.checkVideoPlaying(videoElems[0]);
info('Waiting for second video element to start playing');
await helper.checkVideoPlaying(videoElems[1]);
is(videoElems[0].videoWidth, 50,
"sink is same width as source, modulo our cropping algorithm");
is(videoElems[0].videoHeight, 50,
"sink is same height as source, modulo our cropping algorithm");
is(videoElems[1].videoWidth, 25,
"sink is 1/2 width of source, modulo our cropping algorithm");
is(videoElems[1].videoHeight, 25,
"sink is 1/2 height of source, modulo our cropping algorithm");
await statsReady;
const senderStats = await sender.getStats();
checkSenderStats(senderStats, 2);
checkExpectedFields(senderStats);
pedanticChecks(senderStats);
emitter.stop();
videoStream.getVideoTracks()[0].stop();
offerer.close();
answerer.close();
});
</script>
</pre>
</body>
</html>

View File

@ -49,13 +49,9 @@
}
}
const sendEncodings = [{ rid: "0", maxBitrate: 40000, scaleResolutionDownBy: 1.9 },
{ rid: "1", maxBitrate: 40000, scaleResolutionDownBy: 3.5 },
{ rid: "2", maxBitrate: 40000, scaleResolutionDownBy: 17.8 }];
async function checkSenderStats(sender) {
const senderStats = await sender.getStats();
checkSenderStats(senderStats, sendEncodings.length);
checkSenderStats(senderStats, encodings.length);
checkExpectedFields(senderStats);
pedanticChecks(senderStats);
}
@ -64,7 +60,12 @@
return Promise.all(elements.map(elem => haveEvent(elem, 'resize')));
}
const encodings = [{ rid: "0", maxBitrate: 40000, scaleResolutionDownBy: 1.9 },
{ rid: "1", maxBitrate: 40000, scaleResolutionDownBy: 3.5 },
{ rid: "2", maxBitrate: 40000, scaleResolutionDownBy: 17.8 }];
await pushPrefs(
['media.peerconnection.simulcast', true],
// 180Kbps was determined empirically, set well-higher than
// the 80Kbps+overhead needed for the two simulcast streams.
// 100Kbps was apparently too low.
@ -85,27 +86,18 @@
// One send transceiver, that will be used to send both simulcast streams
const videoStream = emitter.stream();
offerer.addTransceiver(videoStream.getVideoTracks()[0], {sendEncodings});
offerer.addTrack(videoStream.getVideoTracks()[0], videoStream);
const senderElement = document.createElement('video');
senderElement.autoplay = true;
senderElement.srcObject = videoStream;
senderElement.id = videoStream.id
const sender = offerer.getSenders()[0];
let parameters = sender.getParameters();
is(parameters.encodings[0].maxBitrate, sendEncodings[0].maxBitrate);
isfuzzy(parameters.encodings[0].scaleResolutionDownBy,
sendEncodings[0].scaleResolutionDownBy, 0.01);
is(parameters.encodings[1].maxBitrate, sendEncodings[1].maxBitrate);
isfuzzy(parameters.encodings[1].scaleResolutionDownBy,
sendEncodings[1].scaleResolutionDownBy, 0.01);
is(parameters.encodings[2].maxBitrate, sendEncodings[2].maxBitrate);
isfuzzy(parameters.encodings[2].scaleResolutionDownBy,
sendEncodings[2].scaleResolutionDownBy, 0.01);
sender.setParameters({encodings});
const offer = await offerer.createOffer();
const mungedOffer = ridToMid(offer);
const mungedOffer = ridToMid(offer.sdp);
info(`Transformed send simulcast offer to multiple m-sections: ${offer.sdp} to ${mungedOffer}`);
await answerer.setRemoteDescription({type: 'offer', sdp: mungedOffer});
@ -120,7 +112,7 @@
const answer = await answerer.createAnswer();
const mungedAnswer = midToRid(answer);
const mungedAnswer = midToRid(answer.sdp);
info(`Transformed recv answer to simulcast: ${answer.sdp} to ${mungedAnswer}`);
await offerer.setRemoteDescription({type: 'answer', sdp: mungedAnswer});
await answerer.setLocalDescription(answer);
@ -129,7 +121,7 @@
emitter.start();
info('Waiting for 3 loadedmetadata events');
const videoElems = await Promise.all(metadataToBeLoaded);
await checkVideoElements(senderElement, videoElems, parameters.encodings);
await checkVideoElements(senderElement, videoElems, encodings);
emitter.stop();
await Promise.all([waitForSyncedRtcp(offerer), waitForSyncedRtcp(answerer)]);
@ -138,37 +130,34 @@
emitter.size(1280, 720);
emitter.start();
await waitForResizeEvents([senderElement, ...videoElems]);
await checkVideoElements(senderElement, videoElems, parameters.encodings);
await checkVideoElements(senderElement, videoElems, encodings);
await checkSenderStats(sender);
parameters = sender.getParameters();
parameters.encodings[0].scaleResolutionDownBy = 1;
parameters.encodings[1].scaleResolutionDownBy = 2;
parameters.encodings[2].scaleResolutionDownBy = 3;
info(`Changing encodings to ${JSON.stringify(parameters.encodings)}`);
await sender.setParameters(parameters);
encodings[0].scaleResolutionDownBy = 1;
encodings[1].scaleResolutionDownBy = 2;
encodings[2].scaleResolutionDownBy = 3;
info(`Changing encodings to ${JSON.stringify(encodings)}`);
await sender.setParameters({encodings});
await waitForResizeEvents(videoElems);
await checkVideoElements(senderElement, videoElems, parameters.encodings);
await checkVideoElements(senderElement, videoElems, encodings);
await checkSenderStats(sender);
parameters = sender.getParameters();
parameters.encodings[0].scaleResolutionDownBy = 6;
parameters.encodings[1].scaleResolutionDownBy = 5;
parameters.encodings[2].scaleResolutionDownBy = 4;
info(`Changing encodings to ${JSON.stringify(parameters.encodings)}`);
await sender.setParameters(parameters);
encodings[0].scaleResolutionDownBy = 6;
encodings[1].scaleResolutionDownBy = 5;
encodings[2].scaleResolutionDownBy = 4;
info(`Changing encodings to ${JSON.stringify(encodings)}`);
await sender.setParameters({encodings});
await waitForResizeEvents(videoElems);
await checkVideoElements(senderElement, videoElems, parameters.encodings);
await checkVideoElements(senderElement, videoElems, encodings);
await checkSenderStats(sender);
parameters = sender.getParameters();
parameters.encodings[0].scaleResolutionDownBy = 4;
parameters.encodings[1].scaleResolutionDownBy = 1;
parameters.encodings[2].scaleResolutionDownBy = 2;
info(`Changing encodings to ${JSON.stringify(parameters.encodings)}`);
await sender.setParameters(parameters);
encodings[0].scaleResolutionDownBy = 4;
encodings[1].scaleResolutionDownBy = 1;
encodings[2].scaleResolutionDownBy = 2;
info(`Changing encodings to ${JSON.stringify(encodings)}`);
await sender.setParameters({encodings});
await waitForResizeEvents(videoElems);
await checkVideoElements(senderElement, videoElems, parameters.encodings);
await checkVideoElements(senderElement, videoElems, encodings);
await checkSenderStats(sender);
emitter.stop();

View File

@ -1,172 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<script type="application/javascript" src="pc.js"></script>
<script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
<script type="application/javascript" src="helpers_from_wpt/sdp.js"></script>
<script type="application/javascript" src="simulcast.js"></script>
<script type="application/javascript" src="stats.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript">
createHTML({
bug: "1432793",
title: "Simulcast with odd resolution",
visible: true
});
runNetworkTest(async () => {
const helper = new VideoStreamHelper();
const emitter = new VideoFrameEmitter(helper.green, helper.red, 705, 528);
async function checkVideoElement(senderElement, receiverElement, encoding) {
info(`Waiting for receiver video element ${encoding.rid} to start playing`);
await helper.checkVideoPlaying(receiverElement);
const srcWidth = senderElement.videoWidth;
const srcHeight = senderElement.videoHeight;
info(`Source resolution is ${srcWidth}x${srcHeight}`);
const scaleDownBy = encoding.scaleResolutionDownBy;
const expectedWidth = srcWidth / scaleDownBy;
const expectedHeight = srcHeight / scaleDownBy;
const margin = srcWidth * 0.1;
const width = receiverElement.videoWidth;
const height = receiverElement.videoHeight;
const rid = encoding.rid;
ok(width >= expectedWidth - margin && width <= expectedWidth + margin,
`Width ${width} should be within 10% of ${expectedWidth} for rid '${rid}'`);
ok(height >= expectedHeight - margin && height <= expectedHeight + margin,
`Height ${height} should be within 10% of ${expectedHeight} for rid '${rid}'`);
}
async function checkVideoElements(senderElement, receiverElements, encodings) {
is(receiverElements.length, encodings.length, 'Number of video elements should match number of encodings');
info('Waiting for sender video element to start playing');
await helper.checkVideoPlaying(senderElement);
for (let i = 0; i < encodings.length; i++) {
await checkVideoElement(senderElement, receiverElements[i], encodings[i]);
}
}
async function checkSenderStats(sender) {
const senderStats = await sender.getStats();
checkSenderStats(senderStats, encodings.length);
checkExpectedFields(senderStats);
pedanticChecks(senderStats);
}
async function waitForResizeEvents(elements) {
return Promise.all(elements.map(elem => haveEvent(elem, 'resize')));
}
const encodings = [{ rid: "0", maxBitrate: 40000, scaleResolutionDownBy: 1.9 },
{ rid: "1", maxBitrate: 40000, scaleResolutionDownBy: 3.5 },
{ rid: "2", maxBitrate: 40000, scaleResolutionDownBy: 17.8 }];
await pushPrefs(
['media.peerconnection.simulcast', true],
// 180Kbps was determined empirically, set well-higher than
// the 80Kbps+overhead needed for the two simulcast streams.
// 100Kbps was apparently too low.
['media.peerconnection.video.min_bitrate_estimate', 180*1000]);
const offerer = new RTCPeerConnection();
const answerer = new RTCPeerConnection();
const add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed);
offerer.onicecandidate = e => add(answerer, e.candidate, generateErrorCallback());
answerer.onicecandidate = e => add(offerer, e.candidate, generateErrorCallback());
const metadataToBeLoaded = [];
answerer.ontrack = (e) => {
metadataToBeLoaded.push(getPlaybackWithLoadedMetadata(e.track));
};
// One send transceiver, that will be used to send both simulcast streams
const videoStream = emitter.stream();
offerer.addTrack(videoStream.getVideoTracks()[0], videoStream);
const senderElement = document.createElement('video');
senderElement.autoplay = true;
senderElement.srcObject = videoStream;
senderElement.id = videoStream.id
const sender = offerer.getSenders()[0];
sender.setParameters({encodings});
const offer = await offerer.createOffer();
const mungedOffer = ridToMid(offer);
info(`Transformed send simulcast offer to multiple m-sections: ${offer.sdp} to ${mungedOffer}`);
await answerer.setRemoteDescription({type: "offer", sdp: mungedOffer});
await offerer.setLocalDescription(offer);
const rids = answerer.getTransceivers().map(t => t.mid);
is(rids.length, 3, 'Should have 3 mids in offer');
ok(rids[0], 'First mid should be non-empty');
ok(rids[1], 'Second mid should be non-empty');
ok(rids[2], 'Third mid should be non-empty');
info(`rids: ${JSON.stringify(rids)}`);
const answer = await answerer.createAnswer();
const mungedAnswer = midToRid(answer);
info(`Transformed recv answer to simulcast: ${answer.sdp} to ${mungedAnswer}`);
await offerer.setRemoteDescription({type: "answer", sdp: mungedAnswer});
await answerer.setLocalDescription(answer);
is(metadataToBeLoaded.length, 3, 'Offerer should have gotten 3 ontrack events');
emitter.start();
info('Waiting for 3 loadedmetadata events');
const videoElems = await Promise.all(metadataToBeLoaded);
await checkVideoElements(senderElement, videoElems, encodings);
emitter.stop();
await Promise.all([waitForSyncedRtcp(offerer), waitForSyncedRtcp(answerer)]);
info(`Changing source resolution to 1280x720`);
emitter.size(1280, 720);
emitter.start();
await waitForResizeEvents([senderElement, ...videoElems]);
await checkVideoElements(senderElement, videoElems, encodings);
await checkSenderStats(sender);
encodings[0].scaleResolutionDownBy = 1;
encodings[1].scaleResolutionDownBy = 2;
encodings[2].scaleResolutionDownBy = 3;
info(`Changing encodings to ${JSON.stringify(encodings)}`);
await sender.setParameters({encodings});
await waitForResizeEvents(videoElems);
await checkVideoElements(senderElement, videoElems, encodings);
await checkSenderStats(sender);
encodings[0].scaleResolutionDownBy = 6;
encodings[1].scaleResolutionDownBy = 5;
encodings[2].scaleResolutionDownBy = 4;
info(`Changing encodings to ${JSON.stringify(encodings)}`);
await sender.setParameters({encodings});
await waitForResizeEvents(videoElems);
await checkVideoElements(senderElement, videoElems, encodings);
await checkSenderStats(sender);
encodings[0].scaleResolutionDownBy = 4;
encodings[1].scaleResolutionDownBy = 1;
encodings[2].scaleResolutionDownBy = 2;
info(`Changing encodings to ${JSON.stringify(encodings)}`);
await sender.setParameters({encodings});
await waitForResizeEvents(videoElems);
await checkVideoElements(senderElement, videoElems, encodings);
await checkSenderStats(sender);
emitter.stop();
videoStream.getVideoTracks()[0].stop();
offerer.close();
answerer.close();
});
</script>
</pre>
</body>
</html>

View File

@ -19,6 +19,7 @@
runNetworkTest(async () => {
await pushPrefs(
['media.peerconnection.simulcast', true],
// 180Kbps was determined empirically, set well-higher than
// the 80Kbps+overhead needed for the two simulcast streams.
// 100Kbps was apparently too low.
@ -40,18 +41,20 @@
// One send transceiver, that will be used to send both simulcast streams
const emitter = new VideoFrameEmitter();
const videoStream = emitter.stream();
const sendEncodings = [
{ rid: '0', maxBitrate: 40000 },
{ rid: '1', maxBitrate: 40000, scaleResolutionDownBy: 2 }
];
offerer.addTransceiver(videoStream.getVideoTracks()[0], {sendEncodings});
offerer.addTrack(videoStream.getVideoTracks()[0], videoStream);
emitter.start();
const sender = offerer.getSenders()[0];
sender.setParameters({
encodings: [
{ rid: '0', maxBitrate: 40000 },
{ rid: '1', maxBitrate: 40000, scaleResolutionDownBy: 2 }
]
});
const offer = await offerer.createOffer();
const mungedOffer = ridToMid(offer);
const mungedOffer = ridToMid(offer.sdp);
info(`Transformed send simulcast offer to multiple m-sections: ${offer.sdp} to ${mungedOffer}`);
await answerer.setRemoteDescription({type: 'offer', sdp: mungedOffer});
@ -65,7 +68,7 @@
const answer = await answerer.createAnswer();
const mungedAnswer = midToRid(answer);
const mungedAnswer = midToRid(answer.sdp);
info(`Transformed recv answer to simulcast: ${answer.sdp} to ${mungedAnswer}`);
await offerer.setRemoteDescription({type: 'answer', sdp: mungedAnswer});
await answerer.setLocalDescription(answer);

View File

@ -19,6 +19,7 @@
runNetworkTest(async () => {
await pushPrefs(
['media.peerconnection.simulcast', true],
// 180Kbps was determined empirically, set well-higher than
// the 80Kbps+overhead needed for the two simulcast streams.
// 100Kbps was apparently too low.
@ -40,18 +41,20 @@
// One send transceiver, that will be used to send both simulcast streams
const emitter = new VideoFrameEmitter();
const videoStream = emitter.stream();
const sendEncodings = [
{ rid: '0', maxBitrate: 40000, scaleResolutionDownBy: 2 },
{ rid: '1', maxBitrate: 40000 }
];
offerer.addTransceiver(videoStream.getVideoTracks()[0], {sendEncodings});
offerer.addTrack(videoStream.getVideoTracks()[0], videoStream);
emitter.start();
const sender = offerer.getSenders()[0];
sender.setParameters({
encodings: [
{ rid: '0', maxBitrate: 40000, scaleResolutionDownBy: 2 },
{ rid: '1', maxBitrate: 40000 }
]
});
const offer = await offerer.createOffer();
const mungedOffer = ridToMid(offer);
const mungedOffer = ridToMid(offer.sdp);
info(`Transformed send simulcast offer to multiple m-sections: ${offer.sdp} to ${mungedOffer}`);
await answerer.setRemoteDescription({type: 'offer', sdp: mungedOffer});
@ -65,7 +68,7 @@
const answer = await answerer.createAnswer();
const mungedAnswer = midToRid(answer);
const mungedAnswer = midToRid(answer.sdp);
info(`Transformed recv answer to simulcast: ${answer.sdp} to ${mungedAnswer}`);
await offerer.setRemoteDescription({type: 'answer', sdp: mungedAnswer});
await answerer.setLocalDescription(answer);

View File

@ -1,112 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<script type="application/javascript" src="pc.js"></script>
<script type="application/javascript" src="parser_rtp.js"></script>
<script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
<script type="application/javascript" src="helpers_from_wpt/sdp.js"></script>
<script type="application/javascript" src="simulcast.js"></script>
<script type="application/javascript" src="stats.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript">
createHTML({
bug: "1231507",
title: "Basic video-only peer connection with Simulcast offer, first rid has lowest resolution",
visible: true
});
runNetworkTest(async () => {
await pushPrefs(
['media.peerconnection.simulcast', true],
// 180Kbps was determined empirically, set well-higher than
// the 80Kbps+overhead needed for the two simulcast streams.
// 100Kbps was apparently too low.
['media.peerconnection.video.min_bitrate_estimate', 180*1000]);
const offerer = new RTCPeerConnection();
const answerer = new RTCPeerConnection();
const add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed);
offerer.onicecandidate = e => add(answerer, e.candidate, generateErrorCallback());
answerer.onicecandidate = e => add(offerer, e.candidate, generateErrorCallback());
const metadataToBeLoaded = [];
answerer.ontrack = (e) => {
metadataToBeLoaded.push(getPlaybackWithLoadedMetadata(e.track));
};
// One send transceiver, that will be used to send both simulcast streams
const emitter = new VideoFrameEmitter();
const videoStream = emitter.stream();
offerer.addTrack(videoStream.getVideoTracks()[0], videoStream);
emitter.start();
const sender = offerer.getSenders()[0];
sender.setParameters({
encodings: [
{ rid: '0', maxBitrate: 40000, scaleResolutionDownBy: 2 },
{ rid: '1', maxBitrate: 40000 }
]
});
const offer = await offerer.createOffer();
const mungedOffer = ridToMid(offer);
info(`Transformed send simulcast offer to multiple m-sections: ${offer.sdp} to ${mungedOffer}`);
await answerer.setRemoteDescription({type: 'offer', sdp: mungedOffer});
await offerer.setLocalDescription(offer);
const rids = answerer.getTransceivers().map(t => t.mid);
is(rids.length, 2, 'Should have 2 mids in offer');
ok(rids[0] != '', 'First mid should be non-empty');
ok(rids[1] != '', 'Second mid should be non-empty');
info(`rids: ${JSON.stringify(rids)}`);
const answer = await answerer.createAnswer();
const mungedAnswer = midToRid(answer);
info(`Transformed recv answer to simulcast: ${answer.sdp} to ${mungedAnswer}`);
await offerer.setRemoteDescription({type: 'answer', sdp: mungedAnswer});
await answerer.setLocalDescription(answer);
is(metadataToBeLoaded.length, 2, 'Offerer should have gotten 2 ontrack events');
info('Waiting for 2 loadedmetadata events');
const videoElems = await Promise.all(metadataToBeLoaded);
const statsReady =
Promise.all([waitForSyncedRtcp(offerer), waitForSyncedRtcp(answerer)]);
const helper = new VideoStreamHelper();
info('Waiting for first video element to start playing');
await helper.checkVideoPlaying(videoElems[0]);
info('Waiting for second video element to start playing');
await helper.checkVideoPlaying(videoElems[1]);
is(videoElems[1].videoWidth, 50,
"sink is same width as source, modulo our cropping algorithm");
is(videoElems[1].videoHeight, 50,
"sink is same height as source, modulo our cropping algorithm");
is(videoElems[0].videoWidth, 25,
"sink is 1/2 width of source, modulo our cropping algorithm");
is(videoElems[0].videoHeight, 25,
"sink is 1/2 height of source, modulo our cropping algorithm");
await statsReady;
const senderStats = await sender.getStats();
checkSenderStats(senderStats, 2);
checkExpectedFields(senderStats);
pedanticChecks(senderStats);
emitter.stop();
videoStream.getVideoTracks()[0].stop();
offerer.close();
answerer.close();
});
</script>
</pre>
</body>
</html>

View File

@ -1,112 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<script type="application/javascript" src="pc.js"></script>
<script type="application/javascript" src="parser_rtp.js"></script>
<script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
<script type="application/javascript" src="helpers_from_wpt/sdp.js"></script>
<script type="application/javascript" src="simulcast.js"></script>
<script type="application/javascript" src="stats.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript">
createHTML({
bug: "1231507",
title: "Basic video-only peer connection with Simulcast offer",
visible: true
});
runNetworkTest(async () => {
await pushPrefs(
['media.peerconnection.simulcast', true],
// 180Kbps was determined empirically, set well-higher than
// the 80Kbps+overhead needed for the two simulcast streams.
// 100Kbps was apparently too low.
['media.peerconnection.video.min_bitrate_estimate', 180*1000]);
const offerer = new RTCPeerConnection();
const answerer = new RTCPeerConnection();
const add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed);
offerer.onicecandidate = e => add(answerer, e.candidate, generateErrorCallback());
answerer.onicecandidate = e => add(offerer, e.candidate, generateErrorCallback());
const metadataToBeLoaded = [];
answerer.ontrack = (e) => {
metadataToBeLoaded.push(getPlaybackWithLoadedMetadata(e.track));
};
// One send transceiver, that will be used to send both simulcast streams
const emitter = new VideoFrameEmitter();
const videoStream = emitter.stream();
offerer.addTrack(videoStream.getVideoTracks()[0], videoStream);
emitter.start();
const sender = offerer.getSenders()[0];
sender.setParameters({
encodings: [
{ rid: '0', maxBitrate: 40000 },
{ rid: '1', maxBitrate: 40000, scaleResolutionDownBy: 2 }
]
});
const offer = await offerer.createOffer();
const mungedOffer = ridToMid(offer);
info(`Transformed send simulcast offer to multiple m-sections: ${offer.sdp} to ${mungedOffer}`);
await answerer.setRemoteDescription({type: "offer", sdp: mungedOffer});
await offerer.setLocalDescription(offer);
const rids = answerer.getTransceivers().map(t => t.mid);
is(rids.length, 2, 'Should have 2 mids in offer');
ok(rids[0] != '', 'First mid should be non-empty');
ok(rids[1] != '', 'Second mid should be non-empty');
info(`rids: ${JSON.stringify(rids)}`);
const answer = await answerer.createAnswer();
const mungedAnswer = midToRid(answer);
info(`Transformed recv answer to simulcast: ${answer.sdp} to ${mungedAnswer}`);
await offerer.setRemoteDescription({type: "answer", sdp: mungedAnswer});
await answerer.setLocalDescription(answer);
is(metadataToBeLoaded.length, 2, 'Offerer should have gotten 2 ontrack events');
info('Waiting for 2 loadedmetadata events');
const videoElems = await Promise.all(metadataToBeLoaded);
const statsReady =
Promise.all([waitForSyncedRtcp(offerer), waitForSyncedRtcp(answerer)]);
const helper = new VideoStreamHelper();
info('Waiting for first video element to start playing');
await helper.checkVideoPlaying(videoElems[0]);
info('Waiting for second video element to start playing');
await helper.checkVideoPlaying(videoElems[1]);
is(videoElems[0].videoWidth, 50,
"sink is same width as source, modulo our cropping algorithm");
is(videoElems[0].videoHeight, 50,
"sink is same height as source, modulo our cropping algorithm");
is(videoElems[1].videoWidth, 25,
"sink is 1/2 width of source, modulo our cropping algorithm");
is(videoElems[1].videoHeight, 25,
"sink is 1/2 height of source, modulo our cropping algorithm");
await statsReady;
const senderStats = await sender.getStats();
checkSenderStats(senderStats, 2);
checkExpectedFields(senderStats);
pedanticChecks(senderStats);
emitter.stop();
videoStream.getVideoTracks()[0].stop();
offerer.close();
answerer.close();
});
</script>
</pre>
</body>
</html>

View File

@ -132,7 +132,6 @@ interface RTCPeerConnection : EventTarget {
MediaStream... streams);
undefined removeTrack(RTCRtpSender sender);
[Throws]
RTCRtpTransceiver addTransceiver((MediaStreamTrack or DOMString) trackOrKind,
optional RTCRtpTransceiverInit init = {});

View File

@ -32,12 +32,12 @@ dictionary RTCRtpEncodingParameters {
unsigned long ssrc;
RTCRtxParameters rtx;
RTCFecParameters fec;
boolean active = true;
// From https://www.w3.org/TR/webrtc-priority/
RTCPriorityType priority = "low";
boolean active;
RTCPriorityType priority;
unsigned long maxBitrate;
RTCDegradationPreference degradationPreference = "balanced";
DOMString rid;
double scaleResolutionDownBy;
float scaleResolutionDownBy = 1.0;
// From https://w3c.github.io/webrtc-extensions/#rtcrtpencodingparameters-dictionary
double maxFramerate;
};
@ -62,15 +62,8 @@ dictionary RTCRtpCodecParameters {
};
dictionary RTCRtpParameters {
// We do not support these, but every wpt test involving parameters insists
// that these be present, regardless of whether the test-case has anything to
// do with these in particular (see validateRtpParameters).
sequence<RTCRtpEncodingParameters> encodings;
sequence<RTCRtpHeaderExtensionParameters> headerExtensions;
RTCRtcpParameters rtcp;
sequence<RTCRtpCodecParameters> codecs;
};
dictionary RTCRtpSendParameters : RTCRtpParameters {
DOMString transactionId;
required sequence<RTCRtpEncodingParameters> encodings;
};

View File

@ -13,8 +13,8 @@ interface RTCRtpSender {
readonly attribute MediaStreamTrack? track;
readonly attribute RTCDtlsTransport? transport;
[NewObject]
Promise<undefined> setParameters (RTCRtpSendParameters parameters);
RTCRtpSendParameters getParameters();
Promise<undefined> setParameters (optional RTCRtpParameters parameters = {});
RTCRtpParameters getParameters();
[Throws]
Promise<undefined> replaceTrack(MediaStreamTrack? withTrack);
[NewObject]

View File

@ -17,7 +17,8 @@ enum RTCRtpTransceiverDirection {
dictionary RTCRtpTransceiverInit {
RTCRtpTransceiverDirection direction = "sendrecv";
sequence<MediaStream> streams = [];
sequence<RTCRtpEncodingParameters> sendEncodings = [];
// TODO: bug 1396918
// sequence<RTCRtpEncodingParameters> sendEncodings;
};
[Pref="media.peerconnection.enabled",

View File

@ -7054,7 +7054,7 @@ TEST_F(JsepSessionTest, ComplicatedRemoteRollback) {
ASSERT_TRUE(mSessionAns->GetTransceivers()[4]->IsStopped());
ASSERT_FALSE(mSessionAns->GetTransceivers()[4]->IsAssociated());
ASSERT_FALSE(mSessionAns->GetTransceivers()[4]->HasAddTrackMagic());
ASSERT_TRUE(IsNull(mSessionAns->GetTransceivers()[4]->mSendTrack));
ASSERT_FALSE(IsNull(mSessionAns->GetTransceivers()[4]->mSendTrack));
ASSERT_TRUE(mSessionAns->GetTransceivers()[4]->IsRemoved());
// Fourth audio transceiver, created after SetRemote

View File

@ -151,8 +151,7 @@ class JsepTrackTest : public JsepTrackTestBase {
void CreateAnswer() {
if (mRecvAns.GetMediaType() != SdpMediaSection::MediaType::kApplication) {
mRecvAns.RecvTrackSetRemote(*mOffer, GetOffer());
mSendAns.SendTrackSetRemote(mSsrcGenerator, GetOffer());
mRecvAns.UpdateRecvTrack(*mOffer, GetOffer());
}
mSendAns.AddToAnswer(GetOffer(), mSsrcGenerator, &GetAnswer());
@ -161,8 +160,7 @@ class JsepTrackTest : public JsepTrackTestBase {
void Negotiate() {
if (mRecvOff.GetMediaType() != SdpMediaSection::MediaType::kApplication) {
mRecvOff.RecvTrackSetRemote(*mAnswer, GetAnswer());
mSendOff.SendTrackSetRemote(mSsrcGenerator, GetAnswer());
mRecvOff.UpdateRecvTrack(*mAnswer, GetAnswer());
}
if (GetAnswer().IsSending()) {
@ -1275,12 +1273,20 @@ TEST_F(JsepTrackTest, DataChannelDraft21AnswerWithDifferentPort) {
ASSERT_EQ(std::string::npos, mAnswer->ToString().find("a=sctpmap"));
}
static JsepTrack::JsConstraints MakeConstraints(const std::string& rid,
uint32_t maxBitrate) {
JsepTrack::JsConstraints constraints;
constraints.rid = rid;
constraints.constraints.maxBr = maxBitrate;
return constraints;
}
TEST_F(JsepTrackTest, SimulcastRejected) {
Init(SdpMediaSection::kVideo);
std::vector<std::string> rids;
rids.push_back("foo");
rids.push_back("bar");
mSendOff.SetRids(rids);
std::vector<JsepTrack::JsConstraints> constraints;
constraints.push_back(MakeConstraints("foo", 40000));
constraints.push_back(MakeConstraints("bar", 10000));
mSendOff.SetJsConstraints(constraints);
OfferAnswer();
CheckOffEncodingCount(1);
CheckAnsEncodingCount(1);
@ -1288,10 +1294,10 @@ TEST_F(JsepTrackTest, SimulcastRejected) {
TEST_F(JsepTrackTest, SimulcastPrevented) {
Init(SdpMediaSection::kVideo);
std::vector<std::string> rids;
rids.push_back("foo");
rids.push_back("bar");
mSendAns.SetRids(rids);
std::vector<JsepTrack::JsConstraints> constraints;
constraints.push_back(MakeConstraints("foo", 40000));
constraints.push_back(MakeConstraints("bar", 10000));
mSendAns.SetJsConstraints(constraints);
OfferAnswer();
CheckOffEncodingCount(1);
CheckAnsEncodingCount(1);
@ -1299,19 +1305,24 @@ TEST_F(JsepTrackTest, SimulcastPrevented) {
TEST_F(JsepTrackTest, SimulcastOfferer) {
Init(SdpMediaSection::kVideo);
std::vector<std::string> rids;
rids.push_back("foo");
rids.push_back("bar");
mSendOff.SetRids(rids);
std::vector<JsepTrack::JsConstraints> constraints;
constraints.push_back(MakeConstraints("foo", 40000));
constraints.push_back(MakeConstraints("bar", 10000));
mSendOff.SetJsConstraints(constraints);
CreateOffer();
CreateAnswer();
// Add simulcast/rid to answer
mRecvAns.AddToMsection(rids, sdp::kRecv, mSsrcGenerator, false, &GetAnswer());
mRecvAns.AddToMsection(constraints, sdp::kRecv, mSsrcGenerator, false,
&GetAnswer());
Negotiate();
ASSERT_TRUE(mSendOff.GetNegotiatedDetails());
ASSERT_EQ(2U, mSendOff.GetNegotiatedDetails()->GetEncodingCount());
ASSERT_EQ("foo", mSendOff.GetNegotiatedDetails()->GetEncoding(0).mRid);
ASSERT_EQ(40000U,
mSendOff.GetNegotiatedDetails()->GetEncoding(0).mConstraints.maxBr);
ASSERT_EQ("bar", mSendOff.GetNegotiatedDetails()->GetEncoding(1).mRid);
ASSERT_EQ(10000U,
mSendOff.GetNegotiatedDetails()->GetEncoding(1).mConstraints.maxBr);
ASSERT_NE(std::string::npos,
mOffer->ToString().find("a=simulcast:send foo;bar"));
ASSERT_NE(std::string::npos,
@ -1324,16 +1335,19 @@ TEST_F(JsepTrackTest, SimulcastOfferer) {
TEST_F(JsepTrackTest, SimulcastOffererWithRtx) {
Init(SdpMediaSection::kVideo);
std::vector<std::string> rids;
rids.push_back("foo");
rids.push_back("bar");
rids.push_back("pop");
mSendOff.SetRids(rids);
mSendOff.AddToMsection(rids, sdp::kSend, mSsrcGenerator, true, &GetOffer());
mRecvOff.AddToMsection(rids, sdp::kSend, mSsrcGenerator, true, &GetOffer());
std::vector<JsepTrack::JsConstraints> constraints;
constraints.push_back(MakeConstraints("foo", 40000));
constraints.push_back(MakeConstraints("bar", 10000));
constraints.push_back(MakeConstraints("pop", 5000));
mSendOff.SetJsConstraints(constraints);
mSendOff.AddToMsection(constraints, sdp::kSend, mSsrcGenerator, true,
&GetOffer());
mRecvOff.AddToMsection(constraints, sdp::kSend, mSsrcGenerator, true,
&GetOffer());
CreateAnswer();
// Add simulcast/rid to answer
mRecvAns.AddToMsection(rids, sdp::kRecv, mSsrcGenerator, false, &GetAnswer());
mRecvAns.AddToMsection(constraints, sdp::kRecv, mSsrcGenerator, false,
&GetAnswer());
Negotiate();
ASSERT_EQ(3U, mSendOff.GetSsrcs().size());
@ -1353,19 +1367,24 @@ TEST_F(JsepTrackTest, SimulcastOffererWithRtx) {
TEST_F(JsepTrackTest, SimulcastAnswerer) {
Init(SdpMediaSection::kVideo);
std::vector<std::string> rids;
rids.push_back("foo");
rids.push_back("bar");
mSendAns.SetRids(rids);
std::vector<JsepTrack::JsConstraints> constraints;
constraints.push_back(MakeConstraints("foo", 40000));
constraints.push_back(MakeConstraints("bar", 10000));
mSendAns.SetJsConstraints(constraints);
CreateOffer();
// Add simulcast/rid to offer
mRecvOff.AddToMsection(rids, sdp::kRecv, mSsrcGenerator, false, &GetOffer());
mRecvOff.AddToMsection(constraints, sdp::kRecv, mSsrcGenerator, false,
&GetOffer());
CreateAnswer();
Negotiate();
ASSERT_TRUE(mSendAns.GetNegotiatedDetails());
ASSERT_EQ(2U, mSendAns.GetNegotiatedDetails()->GetEncodingCount());
ASSERT_EQ("foo", mSendAns.GetNegotiatedDetails()->GetEncoding(0).mRid);
ASSERT_EQ(40000U,
mSendAns.GetNegotiatedDetails()->GetEncoding(0).mConstraints.maxBr);
ASSERT_EQ("bar", mSendAns.GetNegotiatedDetails()->GetEncoding(1).mRid);
ASSERT_EQ(10000U,
mSendAns.GetNegotiatedDetails()->GetEncoding(1).mConstraints.maxBr);
ASSERT_NE(std::string::npos,
mOffer->ToString().find("a=simulcast:recv foo;bar"));
ASSERT_NE(std::string::npos,
@ -1497,7 +1516,7 @@ TEST_F(JsepTrackTest, RtcpFbWithPayloadTypeAsymmetry) {
mAnswer = std::move(parser->Parse(answer)->Sdp());
ASSERT_TRUE(mAnswer);
mRecvOff.RecvTrackSetRemote(*mAnswer, GetAnswer());
mRecvOff.UpdateRecvTrack(*mAnswer, GetAnswer());
mRecvOff.Negotiate(GetAnswer(), GetAnswer(), GetOffer());
mSendOff.Negotiate(GetAnswer(), GetAnswer(), GetOffer());

View File

@ -5166,6 +5166,20 @@ TEST(NewSdpTestNoFixture, CheckRidValidParse)
ASSERT_EQ(0U, rid.dependIds.size());
}
{
SdpRidAttributeList::Rid rid(ParseRid("0123456789az-_ recv max-width=800"));
ASSERT_EQ("0123456789az-_", rid.id);
ASSERT_EQ(sdp::kRecv, rid.direction);
ASSERT_EQ(0U, rid.formats.size());
ASSERT_EQ(800U, rid.constraints.maxWidth);
ASSERT_EQ(0U, rid.constraints.maxHeight);
ASSERT_FALSE(rid.constraints.maxFps.isSome());
ASSERT_EQ(0U, rid.constraints.maxFs);
ASSERT_EQ(0U, rid.constraints.maxBr);
ASSERT_EQ(0U, rid.constraints.maxPps);
ASSERT_EQ(0U, rid.dependIds.size());
}
{
SdpRidAttributeList::Rid rid(ParseRid("foo send"));
ASSERT_EQ(0U, rid.formats.size());
@ -5416,7 +5430,6 @@ TEST(NewSdpTestNoFixture, CheckRidInvalidParse)
ParseInvalid<SdpRidAttributeList::Rid>("foo send depend=", 16);
ParseInvalid<SdpRidAttributeList::Rid>("foo send depend=,", 16);
ParseInvalid<SdpRidAttributeList::Rid>("foo send depend=1,", 18);
ParseInvalid<SdpRidAttributeList::Rid>("0123456789az-_", 14);
}
TEST(NewSdpTestNoFixture, CheckRidSerialize)

View File

@ -352,8 +352,8 @@ pref("media.videocontrols.keyboard-tab-to-all-controls", true);
pref("media.navigator.audio.fake_frequency", 1000);
pref("media.navigator.permission.disabled", false);
pref("media.navigator.streams.fake", false);
pref("media.peerconnection.simulcast", true);
pref("media.peerconnection.default_iceservers", "[]");
pref("media.peerconnection.allow_old_setParameters", true);
pref("media.peerconnection.ice.loopback", false); // Set only for testing in offline environments.
pref("media.peerconnection.ice.tcp", true);
pref("media.peerconnection.ice.tcp_so_sock_count", 0); // Disable SO gathering

View File

@ -59,8 +59,6 @@ function androidStartup() {
windowTracker.init();
}
Services.fog.initializeFOG();
}
// ///// Desktop ///////

View File

@ -1,2 +1,14 @@
[RTCRtpSendParameters-degradationPreference.html]
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1329847
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[setParameters with invalid degradationPreference should throw TypeError on video transceiver]
expected: FAIL
[setParameters with degradationPreference set should succeed on video transceiver]
expected: FAIL
[setParameters with invalid degradationPreference should throw TypeError on audio transceiver]
expected: FAIL
[setParameters with degradationPreference set should succeed on audio transceiver]
expected: FAIL

View File

@ -0,0 +1,14 @@
[RTCRtpParameters-maxFramerate.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[setParameters() with modified encoding.maxFramerate should succeed with RTCRtpTransceiverInit]
expected: FAIL
[addTransceiver() with sendEncoding.maxFramerate field set to less than 0 should reject with RangeError]
expected: FAIL
[setParameters() with encoding.maxFramerate field set to less than 0 should reject with RangeError]
expected: FAIL
[setParameters() with modified encoding.maxFramerate should succeed without RTCRtpTransceiverInit]
expected: FAIL

View File

@ -1,2 +1,24 @@
[RTCRtpParameters-encodings.html]
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1532658
expected:
if (os == "linux") and not debug and fission and (processor == "x86_64"): [OK, CRASH]
if (os == "android") and fission: [OK, TIMEOUT]
[setParameters() with modified encoding.networkPriority should succeed with RTCRtpTransceiverInit]
expected: FAIL
[setParameters() with modified encoding.priority should succeed with RTCRtpTransceiverInit]
expected: FAIL
[setParameters() with modified encoding.priority should succeed without RTCRtpTransceiverInit]
expected: FAIL
[sender.getParameters() should return sendEncodings set by addTransceiver()]
expected: FAIL
[setParameters() with modified encoding.active should succeed with RTCRtpTransceiverInit]
expected: FAIL
[setParameters() with modified encoding.active should succeed without RTCRtpTransceiverInit]
expected: FAIL
[setParameters() with modified encoding.networkPriority should succeed without RTCRtpTransceiverInit]
expected: FAIL

View File

@ -0,0 +1,10 @@
[RTCPeerConnection-addTransceiver.https.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[addTransceiver() with rid longer than 16 characters should throw TypeError]
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1396918 (Not seeing any spec language about length...)
expected: FAIL
[addTransceiver() with rid containing invalid non-alphanumeric characters should throw TypeError]
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1396918
expected: FAIL

View File

@ -1,6 +1,11 @@
[RTCPeerConnection-transceivers.https.html]
restart-after:
if os == "android": https://bugzilla.mozilla.org/show_bug.cgi?id=1641237
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[addTransceiver(track, init): initialize sendEncodings[0\].active to false]
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1396918
expected: FAIL
[Closing the PC stops the transceivers]
expected: FAIL

View File

@ -0,0 +1,3 @@
[RTCRtpParameters-encodings.html]
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1531458

View File

@ -0,0 +1,3 @@
[RTCRtpParameters-transactionId.html]
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1531458

View File

@ -1,3 +1,3 @@
prefs: [media.navigator.permission.disabled:true, media.navigator.streams.fake:true, privacy.resistFingerprinting.reduceTimerPrecision.jitter:false, privacy.reduceTimerPrecision:false, media.peerconnection.ice.trickle_grace_period:10000, media.peerconnection.ice.obfuscate_host_addresses:false, media.peerconnection.allow_old_setParameters:false]
prefs: [media.navigator.permission.disabled:true, media.navigator.streams.fake:true, privacy.resistFingerprinting.reduceTimerPrecision.jitter:false, privacy.reduceTimerPrecision:false, media.peerconnection.ice.trickle_grace_period:10000, media.peerconnection.ice.obfuscate_host_addresses:false]
lsan-allowed: [Alloc, MakeAndAddRef, Malloc, NS_NewDOMDataChannel, NS_NewRunnableFunction, PR_Realloc, ParentContentActorCreateFunc, allocate, mozilla::DataChannelConnection::Create, mozilla::DataChannelConnection::HandleOpenRequestMessage, mozilla::DataChannelConnection::Open, mozilla::MediaPacket::Copy, mozilla::MediaPipeline::MediaPipeline, mozilla::WeakPtr, mozilla::dom::DocGroup::Create, mozilla::dom::DocGroup::DocGroup, mozilla::runnable_args_func, nsRefPtrDeque, sctp_add_vtag_to_timewait, sctp_hashinit_flags]
leak-threshold: [default:3020800, rdd:51200, tab:51200]

View File

@ -154,6 +154,9 @@
[RTCIceTransport interface: idlTestObjects.iceTransport must inherit property "getRemoteParameters()" with the proper type]
expected: FAIL
[RTCRtpSender interface: operation setParameters(RTCRtpSendParameters)]
expected: FAIL
[RTCIceTransport interface: attribute gatheringState]
expected: FAIL
@ -298,6 +301,9 @@
[RTCPeerConnectionIceErrorEvent interface: attribute errorCode]
expected: FAIL
[RTCRtpSender interface: calling setParameters(RTCRtpSendParameters) on new RTCPeerConnection().addTransceiver('audio').sender with too few arguments must throw TypeError]
expected: FAIL
[RTCIceTransport interface: idlTestObjects.iceTransport must inherit property "gatheringState" with the proper type]
expected: FAIL

View File

@ -1,3 +1,13 @@
[simulcast-answer.html]
max-asserts: 3
expected:
if (os == "android") and fission: [TIMEOUT, OK]
[createAnswer() with multiple send encodings should create simulcast answer]
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1547036
expected: FAIL
[Using the ~rid SDP syntax in a remote offer does not control the local encodings active flag]
expected: FAIL
[Disabling encodings locally does not change the SDP]
expected: FAIL

View File

@ -0,0 +1,7 @@
[simulcast-offer.html]
max-asserts: 3
expected:
if (os == "android") and fission: [TIMEOUT, OK]
[createOffer() with multiple send encodings should create simulcast offer]
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1396918
expected: FAIL

View File

@ -9,3 +9,7 @@
[H.264 and VP8 should be negotiated after handshake]
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1534687
expected: FAIL
[All H.264 codecs MUST include profile-level-id]
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1534687
expected: FAIL

View File

@ -0,0 +1,5 @@
[basic.https.html]
expected:
if (os == "android") and fission: [TIMEOUT, OK]
[Basic simulcast setup with two spatial layers]
expected: FAIL

View File

@ -1,2 +1,6 @@
[getStats.https.html]
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1643001, https://bugzilla.mozilla.org/show_bug.cgi?id=1787474
expected:
if (os == "android") and fission: [TIMEOUT, OK]
[Simulcast getStats results]
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1643001
expected: FAIL

View File

@ -1,9 +1,5 @@
[setParameters-active.https.html]
expected: [OK, TIMEOUT]
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1787474
[Simulcast setParameters active=false on first encoding stops sending frames for that encoding]
expected: [PASS, TIMEOUT]
[Simulcast setParameters active=false on second encoding stops sending frames for that encoding]
expected: [PASS, TIMEOUT, NOTRUN]
expected:
if (os == "android") and fission: [TIMEOUT, OK]
[Simulcast setParameters active=false stops sending frames]
expected: [PASS, TIMEOUT, NOTRUN]
expected: FAIL

View File

@ -8,7 +8,7 @@
<script>
'use strict';
test(t => {
test(function(t) {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
assert_throws_js(RangeError, () => pc.addTransceiver('video', {
@ -18,54 +18,6 @@ test(t => {
}));
}, `addTransceiver() with sendEncoding.maxFramerate field set to less than 0 should reject with RangeError`);
test(t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
let {sender} = pc.addTransceiver('audio', {
sendEncodings: [{
maxFramerate: -10
}]
});
let encodings = sender.getParameters().encodings;
assert_equals(encodings.length, 1);
assert_not_own_property(encodings[0], "maxFramerate");
sender = pc.addTransceiver('audio', {
sendEncodings: [{
maxFramerate: 10
}]
}).sender;
encodings = sender.getParameters().encodings;
assert_equals(encodings.length, 1);
assert_not_own_property(encodings[0], "maxFramerate");
}, `addTransceiver('audio') with sendEncoding.maxFramerate should succeed, but remove the maxFramerate, even if it is invalid`);
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const {sender} = pc.addTransceiver('audio');
let params = sender.getParameters();
assert_equals(params.encodings.length, 1);
params.encodings[0].maxFramerate = 20;
await sender.setParameters(params);
const {encodings} = sender.getParameters();
assert_equals(encodings.length, 1);
assert_not_own_property(encodings[0], "maxFramerate");
}, `setParameters with maxFramerate on an audio sender should succeed, but remove the maxFramerate`);
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const {sender} = pc.addTransceiver('audio');
let params = sender.getParameters();
assert_equals(params.encodings.length, 1);
params.encodings[0].maxFramerate = -1;
await sender.setParameters(params);
const {encodings} = sender.getParameters();
assert_equals(encodings.length, 1);
assert_not_own_property(encodings[0], "maxFramerate");
}, `setParameters with an invalid maxFramerate on an audio sender should succeed, but remove the maxFramerate`);
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
@ -73,29 +25,15 @@ promise_test(async t => {
await doOfferAnswerExchange(t, pc);
const param = sender.getParameters();
const encoding = param.encodings[0];
assert_not_own_property(encoding, "maxFramerate");
validateSenderRtpParameters(param);
const encoding = getFirstEncoding(param);
encoding.maxFramerate = -10;
return promise_rejects_js(t, RangeError,
sender.setParameters(param));
}, `setParameters() with encoding.maxFramerate field set to less than 0 should reject with RangeError`);
// It would be great if we could test to see whether maxFramerate is actually
// honored.
test_modified_encoding('video', 'maxFramerate', 24, 16,
'setParameters() with maxFramerate 24->16 should succeed');
test_modified_encoding('video', 'maxFramerate', undefined, 16,
'setParameters() with maxFramerate undefined->16 should succeed');
test_modified_encoding('video', 'maxFramerate', 24, undefined,
'setParameters() with maxFramerate 24->undefined should succeed');
test_modified_encoding('video', 'maxFramerate', 0, 16,
'setParameters() with maxFramerate 0->16 should succeed');
test_modified_encoding('video', 'maxFramerate', 24, 0,
'setParameters() with maxFramerate 24->0 should succeed');
'setParameters() with modified encoding.maxFramerate should succeed');
</script>

View File

@ -325,7 +325,7 @@
assert_idl_attribute(pc, 'addTransceiver');
assert_throws_js(TypeError, () =>
pc.addTransceiver('video', {
pc.addTransceiver('audio', {
sendEncodings: [{
rid: '@Invalid!'
}]
@ -359,7 +359,7 @@
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
pc.addTransceiver('video', {
pc.addTransceiver('audio', {
sendEncodings: [{
dtx: 'enabled',
active: false,

View File

@ -12,6 +12,7 @@
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
// The following helper functions are called from RTCRtpParameters-helper.js:
// doOfferAnswerExchange
// validateSenderRtpParameters
/*
@ -56,19 +57,6 @@
- encodings is set to the value of the [[send encodings]] internal slot.
*/
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const transceiver = pc.addTransceiver('video');
const param = transceiver.sender.getParameters();
assert_equals(param.encodings.length, 1);
// Do not call this in every test; it does not make sense to disable all of
// the tests below for an implementation that is missing support for
// fields that are not related to the test.
validateSenderRtpParameters(param);
}, `getParameters should return RTCRtpEncodingParameters with all required fields`);
/*
5.1. addTransceiver
7. Create an RTCRtpSender with track, streams and sendEncodings and let sender
@ -87,229 +75,29 @@
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const transceiver = pc.addTransceiver('audio');
await doOfferAnswerExchange(t, pc);
const param = transceiver.sender.getParameters();
validateSenderRtpParameters(param);
const { encodings } = param;
assert_equals(encodings.length, 1);
const encoding = param.encodings[0];
const encoding = getFirstEncoding(param);
assert_equals(encoding.active, true);
assert_not_own_property(encoding, "maxBitrate");
assert_not_own_property(encoding, "rid");
assert_not_own_property(encoding, "scaleResolutionDownBy");
// We do not check props from extension specifications here; those checks
// need to go in a test-case for that extension specification.
}, 'addTransceiver(audio) with undefined sendEncodings should have default encoding parameter with active set to true');
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const transceiver = pc.addTransceiver('video');
const param = transceiver.sender.getParameters();
const { encodings } = param;
assert_equals(encodings.length, 1);
const encoding = param.encodings[0];
assert_equals(encoding.active, true);
// spec says to return an encoding without a scaleResolutionDownBy value
// when addTransceiver does not pass any encodings, however spec also says
// to throw if setParameters is missing a scaleResolutionDownBy. One of
// these two requirements needs to be removed, but it is unclear right now
// which will be removed. For now, allow scaleResolutionDownBy, but don't
// require it.
// https://github.com/w3c/webrtc-pc/issues/2730
assert_not_own_property(encoding, "maxBitrate");
assert_not_own_property(encoding, "rid");
assert_equals(encoding.scaleResolutionDownBy, 1.0);
// We do not check props from extension specifications here; those checks
// need to go in a test-case for that extension specification.
}, 'addTransceiver(video) with undefined sendEncodings should have default encoding parameter with active set to true and scaleResolutionDownBy set to 1');
}, 'addTransceiver() with undefined sendEncodings should have default encoding parameter with active set to true');
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const transceiver = pc.addTransceiver('audio', { sendEncodings: [] });
await doOfferAnswerExchange(t, pc);
const param = transceiver.sender.getParameters();
validateSenderRtpParameters(param);
const { encodings } = param;
assert_equals(encodings.length, 1);
const encoding = param.encodings[0];
const encoding = getFirstEncoding(param);
assert_equals(encoding.active, true);
assert_not_own_property(encoding, "maxBitrate");
assert_not_own_property(encoding, "rid");
assert_not_own_property(encoding, "scaleResolutionDownBy");
// We do not check props from extension specifications here; those checks
// need to go in a test-case for that extension specification.
}, 'addTransceiver(audio) with empty list sendEncodings should have default encoding parameter with active set to true');
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const transceiver = pc.addTransceiver('video', { sendEncodings: [] });
const param = transceiver.sender.getParameters();
const { encodings } = param;
assert_equals(encodings.length, 1);
const encoding = param.encodings[0];
assert_equals(encoding.active, true);
assert_not_own_property(encoding, "maxBitrate");
assert_not_own_property(encoding, "rid");
assert_equals(encoding.scaleResolutionDownBy, 1.0);
// We do not check props from extension specifications here; those checks
// need to go in a test-case for that extension specification.
}, 'addTransceiver(video) with empty list sendEncodings should have default encoding parameter with active set to true and scaleResolutionDownBy set to 1');
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const transceiver = pc.addTransceiver('video', {sendEncodings: [{rid: "foo"}, {rid: "bar", scaleResolutionDownBy: 3.0}]});
const param = transceiver.sender.getParameters();
const { encodings } = param;
assert_equals(encodings.length, 2);
assert_equals(encodings[0].scaleResolutionDownBy, 1.0);
assert_equals(encodings[1].scaleResolutionDownBy, 3.0);
}, `addTransceiver(video) should auto-set scaleResolutionDownBy to 1 when some encodings have it, but not all`);
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const transceiver = pc.addTransceiver('video', {sendEncodings: [{rid: "foo"}, {rid: "bar"}]});
const param = transceiver.sender.getParameters();
const { encodings } = param;
assert_equals(encodings.length, 2);
assert_equals(encodings[0].scaleResolutionDownBy, 2.0);
assert_equals(encodings[1].scaleResolutionDownBy, 1.0);
}, `addTransceiver should auto-set scaleResolutionDownBy to powers of 2 (descending) when absent`);
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const sendEncodings = [];
for (let i = 0; i < 1000; i++) {
sendEncodings.push({rid: i});
}
const transceiver = pc.addTransceiver('video', {sendEncodings});
const param = transceiver.sender.getParameters();
const { encodings } = param;
assert_less_than(encodings.length, 1000, `1000 encodings is clearly too many`);
}, `addTransceiver with a ridiculous number of encodings should truncate the list`);
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const transceiver = pc.addTransceiver('audio', {sendEncodings: [{rid: "foo"}, {rid: "bar"}]});
const param = transceiver.sender.getParameters();
const { encodings } = param;
assert_equals(encodings.length, 1);
assert_not_own_property(encodings[0], "maxBitrate");
assert_not_own_property(encodings[0], "rid");
assert_not_own_property(encodings[0], "scaleResolutionDownBy");
// We do not check props from extension specifications here; those checks
// need to go in a test-case for that extension specification.
}, `addTransceiver(audio) with multiple encodings should result in one encoding with no properties other than active`);
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const {sender} = pc.addTransceiver('audio', {sendEncodings: [{rid: "foo", scaleResolutionDownBy: 2.0}]});
const {encodings} = sender.getParameters();
assert_equals(encodings.length, 1);
assert_not_own_property(encodings[0], "scaleResolutionDownBy");
}, `addTransceiver(audio) should remove valid scaleResolutionDownBy`);
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const {sender} = pc.addTransceiver('audio', {sendEncodings: [{rid: "foo", scaleResolutionDownBy: -1.0}]});
const {encodings} = sender.getParameters();
assert_equals(encodings.length, 1);
assert_not_own_property(encodings[0], "scaleResolutionDownBy");
}, `addTransceiver(audio) should remove invalid scaleResolutionDownBy`);
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const {sender} = pc.addTransceiver('audio');
let params = sender.getParameters();
assert_equals(params.encodings.length, 1);
params.encodings[0].scaleResolutionDownBy = 2;
await sender.setParameters(params);
const {encodings} = sender.getParameters();
assert_equals(encodings.length, 1);
assert_not_own_property(encodings[0], "scaleResolutionDownBy");
}, `setParameters with scaleResolutionDownBy on an audio sender should succeed, but remove the scaleResolutionDownBy`);
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const {sender} = pc.addTransceiver('audio');
let params = sender.getParameters();
assert_equals(params.encodings.length, 1);
params.encodings[0].scaleResolutionDownBy = -1;
await sender.setParameters(params);
const {encodings} = sender.getParameters();
assert_equals(encodings.length, 1);
assert_not_own_property(encodings[0], "scaleResolutionDownBy");
}, `setParameters with an invalid scaleResolutionDownBy on an audio sender should succeed, but remove the scaleResolutionDownBy`);
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
assert_throws_js(TypeError, () => pc.addTransceiver('video', { sendEncodings: [{rid: "foo"}, {rid: "foo"}] }));
}, 'addTransceiver with duplicate rid and multiple encodings throws TypeError');
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
assert_throws_js(TypeError, () => pc.addTransceiver('video', { sendEncodings: [{rid: "foo"}, {}] }));
}, 'addTransceiver with missing rid and multiple encodings throws TypeError');
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
assert_throws_js(TypeError, () => pc.addTransceiver('video', { sendEncodings: [{rid: ""}] }));
}, 'addTransceiver with empty rid throws TypeError');
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
assert_throws_js(TypeError, () => pc.addTransceiver('video', { sendEncodings: [{rid: "!?"}] }));
assert_throws_js(TypeError, () => pc.addTransceiver('video', { sendEncodings: [{rid: "(╯°□°)╯︵ ┻━┻"}] }));
// RFC 8851 says '-' and '_' are allowed, but RFC 8852 says they are not.
// RFC 8852 needs to be adhered to, otherwise we can't put the rid in RTP
// https://github.com/w3c/webrtc-pc/issues/2732
// https://www.rfc-editor.org/errata/eid7132
assert_throws_js(TypeError, () => pc.addTransceiver('video', { sendEncodings: [{rid: "foo-bar"}] }));
assert_throws_js(TypeError, () => pc.addTransceiver('video', { sendEncodings: [{rid: "foo_bar"}] }));
}, 'addTransceiver with invalid rid characters throws TypeError');
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
// https://github.com/w3c/webrtc-pc/issues/2732
assert_throws_js(TypeError, () => pc.addTransceiver('video', { sendEncodings: [{rid: 'a'.repeat(256)}] }));
}, 'addTransceiver with rid longer than 255 characters throws TypeError');
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
assert_throws_js(RangeError, () => pc.addTransceiver('video', { sendEncodings: [{scaleResolutionDownBy: -1}] }));
assert_throws_js(RangeError, () => pc.addTransceiver('video', { sendEncodings: [{scaleResolutionDownBy: 0}] }));
assert_throws_js(RangeError, () => pc.addTransceiver('video', { sendEncodings: [{scaleResolutionDownBy: 0.5}] }));
}, `addTransceiver with scaleResolutionDownBy < 1 throws RangeError`);
}, 'addTransceiver() with empty list sendEncodings should have default encoding parameter with active set to true');
/*
5.2. create an RTCRtpSender
@ -335,9 +123,11 @@
rid: 'foo'
}]
});
await doOfferAnswerExchange(t, pc);
const param = sender.getParameters();
const encoding = param.encodings[0];
validateSenderRtpParameters(param);
const encoding = getFirstEncoding(param);
assert_equals(encoding.active, false);
assert_equals(encoding.maxBitrate, 8);
@ -359,64 +149,33 @@
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const { sender } = pc.addTransceiver('video');
const { sender } = pc.addTransceiver('audio');
await doOfferAnswerExchange(t, pc);
const param = sender.getParameters();
validateSenderRtpParameters(param);
const { encodings } = param;
assert_equals(encodings.length, 1);
// While {} is valid RTCRtpEncodingParameters because all fields are
// optional, it is still invalid to be missing a rid when there are multiple
// encodings. Only trigger one kind of error here.
encodings.push({ rid: "foo" });
// {} is valid RTCRtpEncodingParameters because all fields are optional
encodings.push({});
assert_equals(param.encodings.length, 2);
return promise_rejects_dom(t, 'InvalidModificationError',
sender.setParameters(param));
}, `sender.setParameters() with added encodings should reject with InvalidModificationError`);
}, `sender.setParameters() with mismatch number of encodings should reject with InvalidModificationError`);
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const { sender } = pc.addTransceiver('video', {sendEncodings: [{rid: "foo"}, {rid: "bar"}]});
const { sender } = pc.addTransceiver('audio');
await doOfferAnswerExchange(t, pc);
const param = sender.getParameters();
validateSenderRtpParameters(param);
const { encodings } = param;
assert_equals(encodings.length, 2);
encodings.pop();
assert_equals(param.encodings.length, 1);
return promise_rejects_dom(t, 'InvalidModificationError',
sender.setParameters(param));
}, `sender.setParameters() with removed encodings should reject with InvalidModificationError`);
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const { sender } = pc.addTransceiver('video', {sendEncodings: [{rid: "foo"}, {rid: "bar"}]});
const param = sender.getParameters();
const { encodings } = param;
assert_equals(encodings.length, 2);
encodings.push(encodings.shift());
assert_equals(param.encodings.length, 2);
return promise_rejects_dom(t, 'InvalidModificationError',
sender.setParameters(param));
}, `sender.setParameters() with reordered encodings should reject with InvalidModificationError`);
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const { sender } = pc.addTransceiver('video');
const param = sender.getParameters();
delete param.encodings;
param.encodings = undefined;
return promise_rejects_js(t, TypeError,
sender.setParameters(param));
@ -428,9 +187,11 @@
const { sender } = pc.addTransceiver('video', {
sendEncodings: [{ rid: 'foo' }, { rid: 'baz' }],
});
await doOfferAnswerExchange(t, pc);
const param = sender.getParameters();
const encoding = param.encodings[0];
validateSenderRtpParameters(param);
const encoding = getFirstEncoding(param);
assert_equals(encoding.rid, 'foo');
@ -448,68 +209,44 @@
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const { sender } = pc.addTransceiver('video');
const { sender } = pc.addTransceiver('audio');
await doOfferAnswerExchange(t, pc);
const param = sender.getParameters();
const encoding = param.encodings[0];
validateSenderRtpParameters(param);
const encoding = getFirstEncoding(param);
encoding.scaleResolutionDownBy = 0.5;
await promise_rejects_js(t, RangeError, sender.setParameters(param));
encoding.scaleResolutionDownBy = 0;
await promise_rejects_js(t, RangeError, sender.setParameters(param));
encoding.scaleResolutionDownBy = -1;
await promise_rejects_js(t, RangeError, sender.setParameters(param));
return promise_rejects_js(t, RangeError,
sender.setParameters(param));
}, `setParameters() with encoding.scaleResolutionDownBy field set to less than 1.0 should reject with RangeError`);
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const { sender } = pc.addTransceiver('video');
let param = sender.getParameters();
const encoding = param.encodings[0];
delete encoding.scaleResolutionDownBy;
await sender.setParameters(param);
param = sender.getParameters();
assert_equals(param.encodings[0].scaleResolutionDownBy, 1.0);
}, `setParameters() with missing encoding.scaleResolutionDownBy field should succeed, and set the value back to 1`);
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const { sender } = pc.addTransceiver('video');
await doOfferAnswerExchange(t, pc);
const param = sender.getParameters();
const encoding = param.encodings[0];
validateSenderRtpParameters(param);
const encoding = getFirstEncoding(param);
encoding.scaleResolutionDownBy = 1.5;
return sender.setParameters(param)
.then(() => {
const param = sender.getParameters();
const encoding = param.encodings[0];
validateSenderRtpParameters(param);
const encoding = getFirstEncoding(param);
assert_approx_equals(encoding.scaleResolutionDownBy, 1.5, 0.01);
});
}, `setParameters() with encoding.scaleResolutionDownBy field set to greater than 1.0 should succeed`);
test_modified_encoding('video', 'active', false, true,
'setParameters() with encoding.active false->true should succeed (video)');
test_modified_encoding('video', 'active', true, false,
'setParameters() with encoding.active true->false should succeed (video)');
test_modified_encoding('video', 'maxBitrate', 10000, 20000,
'setParameters() with modified encoding.maxBitrate should succeed (video)');
test_modified_encoding('audio', 'active', false, true,
'setParameters() with encoding.active false->true should succeed (audio)');
test_modified_encoding('audio', 'active', true, false,
'setParameters() with encoding.active true->false should succeed (audio)');
'setParameters() with modified encoding.active should succeed');
test_modified_encoding('audio', 'maxBitrate', 10000, 20000,
'setParameters() with modified encoding.maxBitrate should succeed (audio)');
'setParameters() with modified encoding.maxBitrate should succeed');
test_modified_encoding('video', 'scaleResolutionDownBy', 2, 4,
'setParameters() with modified encoding.scaleResolutionDownBy should succeed');

View File

@ -202,6 +202,16 @@ function validateCodecParameters(codec) {
assert_optional_string_field(codec, 'sdpFmtpLine');
}
// Get the first encoding in param.encodings.
// Asserts that param.encodings has at least one element.
function getFirstEncoding(param) {
const {
encodings
} = param;
assert_equals(encodings.length, 1);
return encodings[0];
}
// Helper function to test that modifying an encoding field should succeed
function test_modified_encoding(kind, field, value1, value2, desc) {
promise_test(async t => {
@ -218,7 +228,7 @@ function test_modified_encoding(kind, field, value1, value2, desc) {
const param1 = sender.getParameters();
validateSenderRtpParameters(param1);
const encoding1 = param1.encodings[0];
const encoding1 = getFirstEncoding(param1);
assert_equals(encoding1[field], value1);
encoding1[field] = value2;
@ -226,7 +236,7 @@ function test_modified_encoding(kind, field, value1, value2, desc) {
await sender.setParameters(param1);
const param2 = sender.getParameters();
validateSenderRtpParameters(param2);
const encoding2 = param2.encodings[0];
const encoding2 = getFirstEncoding(param2);
assert_equals(encoding2[field], value2);
}, desc + ' with RTCRtpTransceiverInit');
@ -245,7 +255,7 @@ function test_modified_encoding(kind, field, value1, value2, desc) {
const param1 = sender.getParameters();
validateSenderRtpParameters(param1);
const encoding1 = param1.encodings[0];
const encoding1 = getFirstEncoding(param1);
assert_equals(encoding1[field], value1);
encoding1[field] = value2;
@ -253,7 +263,7 @@ function test_modified_encoding(kind, field, value1, value2, desc) {
await sender.setParameters(param1);
const param2 = sender.getParameters();
validateSenderRtpParameters(param2);
const encoding2 = param2.encodings[0];
const encoding2 = getFirstEncoding(param2);
assert_equals(encoding2[field], value2);
}, desc + ' without RTCRtpTransceiverInit');
}

View File

@ -11,6 +11,10 @@
// Test is based on the following editor draft:
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
// The following helper functions are called from RTCRtpParameters-helper.js:
// doOfferAnswerExchange
// validateSenderRtpParameters
/*
5.1. RTCPeerConnection Interface Extensions
partial interface RTCPeerConnection {
@ -60,53 +64,16 @@
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const { sender } = pc.addTransceiver('audio');
await doOfferAnswerExchange(t, pc);
const param1 = sender.getParameters();
const param2 = sender.getParameters();
assert_equals(typeof param1.transactionId, "string");
assert_greater_than(param1.transactionId.length, 0);
assert_equals(typeof param2.transactionId, "string");
assert_greater_than(param2.transactionId.length, 0);
assert_equals(param1.transactionId, param2.transactionId);
await undefined;
const param3 = sender.getParameters();
assert_equals(typeof param3.transactionId, "string");
assert_greater_than(param3.transactionId.length, 0);
assert_equals(param1.transactionId, param3.transactionId);
}, `sender.getParameters() should return the same transaction ID if called back-to-back without relinquishing the event loop, even if the microtask queue runs`);
test(t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const { sender } = pc.addTransceiver('audio');
const param1 = sender.getParameters();
sender.setParameters(param1);
const param2 = sender.getParameters();
assert_equals(typeof param1.transactionId, "string");
assert_greater_than(param1.transactionId.length, 0);
assert_equals(typeof param2.transactionId, "string");
assert_greater_than(param2.transactionId.length, 0);
assert_equals(param1.transactionId, param2.transactionId);
}, `sender.getParameters() should return the same transaction ID if called back-to-back without relinquishing the event loop, even if there is an intervening call to setParameters`);
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const { sender } = pc.addTransceiver('audio');
const param1 = sender.getParameters();
await pc.createOffer();
const param2 = sender.getParameters();
assert_equals(typeof param1.transactionId, "string");
assert_greater_than(param1.transactionId.length, 0);
assert_equals(typeof param2.transactionId, "string");
assert_greater_than(param2.transactionId.length, 0);
validateSenderRtpParameters(param1);
validateSenderRtpParameters(param2);
assert_not_equals(param1.transactionId, param2.transactionId);
}, `sender.getParameters() should return a different transaction ID if the event loop is relinquished between multiple calls`);
}, `sender.getParameters() should return different transaction IDs for each call`);
/*
5.2. setParameters
@ -121,13 +88,15 @@
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const { sender } = pc.addTransceiver('audio');
await doOfferAnswerExchange(t, pc);
const param = sender.getParameters();
validateSenderRtpParameters(param);
const { transactionId } = param;
param.transactionId = `${transactionId}-modified`;
await promise_rejects_dom(t, 'InvalidModificationError',
return promise_rejects_dom(t, 'InvalidModificationError',
sender.setParameters(param));
}, `sender.setParameters() with transaction ID different from last getParameters() should reject with InvalidModificationError`);
@ -135,12 +104,14 @@
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const { sender } = pc.addTransceiver('audio');
await doOfferAnswerExchange(t, pc);
const param = sender.getParameters();
validateSenderRtpParameters(param);
delete param.transactionId;
param.transactionId = undefined;
await promise_rejects_js(t, TypeError,
return promise_rejects_js(t, TypeError,
sender.setParameters(param));
}, `sender.setParameters() with transaction ID unset should reject with TypeError`);
@ -148,39 +119,33 @@
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const { sender } = pc.addTransceiver('audio');
await doOfferAnswerExchange(t, pc);
const param = sender.getParameters();
validateSenderRtpParameters(param);
await sender.setParameters(param);
await promise_rejects_dom(t, 'InvalidStateError', sender.setParameters(param))
return sender.setParameters(param)
.then(() =>
promise_rejects_dom(t, 'InvalidStateError',
sender.setParameters(param)));
}, `setParameters() twice with the same parameters should reject with InvalidStateError`);
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const { sender } = pc.addTransceiver('audio');
await doOfferAnswerExchange(t, pc);
const param1 = sender.getParameters();
// Queue a task, does not really matter what kind
await pc.createOffer();
const param2 = sender.getParameters();
validateSenderRtpParameters(param1);
validateSenderRtpParameters(param2);
assert_not_equals(param1.transactionId, param2.transactionId);
await promise_rejects_dom(t, 'InvalidModificationError',
return promise_rejects_dom(t, 'InvalidModificationError',
sender.setParameters(param1));
}, `setParameters() with parameters older than last getParameters() should reject with InvalidModificationError`);
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const { sender } = pc.addTransceiver('audio');
const param1 = sender.getParameters();
await pc.createOffer();
await promise_rejects_dom(t, 'InvalidStateError',
sender.setParameters(param1));
}, `setParameters() when the event loop has been relinquished since the last getParameters() should reject with InvalidStateError`);
</script>

View File

@ -1,436 +0,0 @@
<!doctype html>
<meta charset=utf-8>
<title>RTCPeerConnection Simulcast Tests - negotiation/encodings</title>
<meta name="timeout" content="long">
<script src="../third_party/sdp/sdp.js"></script>
<script src="simulcast.js"></script>
<script src="../RTCPeerConnection-helper.js"></script>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="../../mediacapture-streams/permission-helper.js"></script>
<script>
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
const stream = await getNoiseStream({video: true});
t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
const sender = pc1.addTrack(stream.getTracks()[0]);
// pc1 is unicast right now
pc2.addTrack(stream.getTracks()[0]);
await doOfferToRecvSimulcastAndAnswer(pc2, pc1, ["foo", "bar"]);
assert_equals(pc1.getTransceivers().length, 1);
const {encodings} = sender.getParameters();
const rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "bar"]);
}, 'addTrack, then sRD(simulcast recv offer) results in simulcast');
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
const stream = await getNoiseStream({audio: true});
t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
const sender = pc1.addTrack(stream.getTracks()[0]);
// pc1 is unicast right now
pc2.addTrack(stream.getTracks()[0]);
await doOfferToRecvSimulcastAndAnswer(pc2, pc1, ["foo", "bar"]);
assert_equals(pc1.getTransceivers().length, 1);
const {encodings} = sender.getParameters();
const rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, [undefined]);
}, 'simulcast is not supported for audio');
// We do not have a test case for sRD(offer) narrowing a simulcast envelope
// from addTransceiver, since that transceiver cannot be paired up with a remote
// offer m-section
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
const {sender} = pc1.addTransceiver("video", {sendEncodings: [{rid: "foo"}, {rid: "bar"}]});
let {encodings} = sender.getParameters();
await doOfferToSendSimulcastAndAnswer(pc1, pc2, ["foo"]);
assert_equals(pc1.getTransceivers().length, 1);
encodings = sender.getParameters().encodings;
const rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo"]);
const scaleDownByValues = encodings.map(({scaleResolutionDownBy}) => scaleResolutionDownBy);
assert_array_equals(scaleDownByValues, [2]);
}, 'sRD(recv simulcast answer) can narrow the simulcast envelope specified by addTransceiver');
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
const {sender} = pc1.addTransceiver("video", {sendEncodings: [{rid: "foo"}, {rid: "bar"}]});
let {encodings} = sender.getParameters();
await doOfferToSendSimulcastAndAnswer(pc1, pc2, ["foo", "bar"]);
assert_equals(pc1.getTransceivers().length, 1);
encodings = sender.getParameters().encodings;
let rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "bar"]);
await doOfferToSendSimulcastAndAnswer(pc1, pc2, ["foo"]);
assert_equals(pc1.getTransceivers().length, 1);
encodings = sender.getParameters().encodings;
rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo"]);
const scaleDownByValues = encodings.map(({scaleResolutionDownBy}) => scaleResolutionDownBy);
assert_array_equals(scaleDownByValues, [2]);
}, 'sRD(recv simulcast answer) can narrow the simulcast envelope from a previous negotiation');
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
const {sender} = pc1.addTransceiver("video", {sendEncodings: [{rid: "foo"}, {rid: "bar"}]});
let {encodings} = sender.getParameters();
await doOfferToSendSimulcastAndAnswer(pc1, pc2, ["foo", "bar"]);
assert_equals(pc1.getTransceivers().length, 1);
encodings = sender.getParameters().encodings;
let rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "bar"]);
// doAnswerToSendSimulcast causes pc2 to barf unless we set the direction to
// sendrecv
pc2.getTransceivers()[0].direction = "sendrecv";
pc2.getTransceivers()[1].direction = "sendrecv";
await doOfferToRecvSimulcast(pc2, pc1, ["foo"]);
assert_equals(pc1.getTransceivers().length, 1);
encodings = sender.getParameters().encodings;
rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "bar"], "[[SendEncodings]] is not updated in have-remote-offer for reoffers");
await doAnswerToSendSimulcast(pc2, pc1);
assert_equals(pc1.getTransceivers().length, 1);
encodings = sender.getParameters().encodings;
rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo"]);
const scaleDownByValues = encodings.map(({scaleResolutionDownBy}) => scaleResolutionDownBy);
assert_array_equals(scaleDownByValues, [2]);
}, 'sRD(simulcast offer) can narrow the simulcast envelope from a previous negotiation');
// https://github.com/w3c/webrtc-pc/issues/2780
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
const stream = await getNoiseStream({video: true});
t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
const sender = pc1.addTrack(stream.getTracks()[0]);
pc2.addTrack(stream.getTracks()[0]);
await doOfferToRecvSimulcastAndAnswer(pc2, pc1, ["foo", "bar", "foo"]);
assert_equals(pc1.getTransceivers().length, 1);
let {encodings} = sender.getParameters();
let rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "bar"]);
assert_true(pc1.remoteDescription.sdp.includes("a=simulcast:recv foo;bar;foo"), "Duplicate rids should be present in offer");
assert_false(pc1.localDescription.sdp.includes("a=simulcast:send foo;bar;foo"), "Duplicate rids should not be present in answer");
assert_true(pc1.localDescription.sdp.includes("a=simulcast:send foo;bar"), "Answer should use the correct rids");
assert_equals(pc1.getTransceivers().length, 1);
encodings = sender.getParameters().encodings;
rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "bar"]);
}, 'Duplicate rids in sRD(offer) are ignored');
// https://github.com/w3c/webrtc-pc/issues/2769
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
const stream = await getNoiseStream({video: true});
t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
const sender = pc1.addTrack(stream.getTracks()[0]);
pc2.addTrack(stream.getTracks()[0]);
await doOfferToRecvSimulcastAndAnswer(pc2, pc1, ["foo,bar", "1,2"]);
assert_equals(pc1.getTransceivers().length, 1);
let {encodings} = sender.getParameters();
let rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "1"]);
assert_true(pc1.remoteDescription.sdp.includes("a=simulcast:recv foo,bar;1,2"), "Choices of rids should be present in offer");
assert_true(pc1.localDescription.sdp.includes("a=simulcast:send foo;1\r\n"), "Choices of rids should not be present in answer");
assert_equals(pc1.getTransceivers().length, 1);
encodings = sender.getParameters().encodings;
rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "1"]);
}, 'Choices in rids in sRD(offer) are ignored');
// https://github.com/w3c/webrtc-pc/issues/2764
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
const stream = await getNoiseStream({video: true});
t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
const sender = pc1.addTrack(stream.getTracks()[0]);
pc2.addTrack(stream.getTracks()[0]);
await doOfferToRecvSimulcast(pc2, pc1, ["foo", "bar"]);
assert_equals(pc1.getTransceivers().length, 1);
let {encodings} = sender.getParameters();
let rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "bar"]);
await pc1.setRemoteDescription({sdp: "", type: "rollback"});
assert_equals(pc1.getTransceivers().length, 1);
encodings = sender.getParameters().encodings;
rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, [undefined]);
}, 'addTrack, then rollback of sRD(simulcast offer), brings us back to having a single encoding without a rid');
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
const stream = await getNoiseStream({video: true});
t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
pc2.addTrack(stream.getTracks()[0]);
await doOfferToRecvSimulcast(pc2, pc1, ["foo", "bar"]);
const sender = pc1.addTrack(stream.getTracks()[0]);
assert_equals(pc1.getTransceivers().length, 1);
assert_equals(pc1.getTransceivers().length, 1);
let {encodings} = sender.getParameters();
let rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "bar"]);
await pc1.setRemoteDescription({sdp: "", type: "rollback"});
assert_equals(pc1.getTransceivers().length, 1);
encodings = sender.getParameters().encodings;
rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, [undefined]);
}, 'sRD(simulcast offer), addTrack, then rollback brings us back to having a single encoding');
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
const stream = await getNoiseStream({video: true});
t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
const {sender} = pc1.addTransceiver(stream.getTracks()[0], {sendEncodings: [{rid: "foo"}, {rid: "bar"}]});
await doOfferToSendSimulcast(pc1, pc2);
await doAnswerToRecvSimulcast(pc1, pc2, ["bar", "foo"]);
assert_true(pc1.remoteDescription.sdp.includes("a=simulcast:recv bar;foo"), "Answer should have reordered rids");
assert_equals(pc1.getTransceivers().length, 1);
const {encodings} = sender.getParameters();
const rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "bar"]);
}, 'Reordering of rids in sRD(answer) is ignored');
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
const stream = await getNoiseStream({video: true});
t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
const {sender} = pc1.addTransceiver(stream.getTracks()[0], {sendEncodings: [{rid: "foo"}, {rid: "bar"}]});
await doOfferToSendSimulcastAndAnswer(pc1, pc2, ["foo", "bar"]);
assert_equals(pc1.getTransceivers().length, 1);
let {encodings} = sender.getParameters();
let rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "bar"]);
await doOfferToSendSimulcast(pc1, pc2);
await doAnswerToRecvSimulcast(pc1, pc2, ["bar", "foo"]);
assert_true(pc1.remoteDescription.sdp.includes("a=simulcast:recv bar;foo"), "Answer should have reordered rids");
assert_equals(pc1.getTransceivers().length, 1);
encodings = sender.getParameters().encodings;
rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "bar"]);
}, 'Reordering of rids in sRD(reanswer) is ignored');
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
const stream = await getNoiseStream({video: true});
t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
const {sender} = pc1.addTransceiver(stream.getTracks()[0], {sendEncodings: [{rid: "foo"}, {rid: "bar"}]});
await doOfferToSendSimulcastAndAnswer(pc1, pc2, ["foo", "bar"]);
assert_equals(pc1.getTransceivers().length, 1);
let {encodings} = sender.getParameters();
let rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "bar"]);
// doAnswerToSendSimulcast causes pc2 to barf unless we set the direction to
// sendrecv
pc2.getTransceivers()[0].direction = "sendrecv";
pc2.getTransceivers()[1].direction = "sendrecv";
await doOfferToRecvSimulcast(pc2, pc1, ["bar", "foo"]);
await doAnswerToSendSimulcast(pc2, pc1);
assert_true(pc1.remoteDescription.sdp.includes("a=simulcast:recv bar;foo"), "Reoffer should have reordered rids");
assert_equals(pc1.getTransceivers().length, 1);
encodings = sender.getParameters().encodings;
rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "bar"]);
}, 'Reordering of rids in sRD(reoffer) is ignored');
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
const stream = await getNoiseStream({video: true});
t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
const {sender} = pc1.addTransceiver(stream.getTracks()[0], {sendEncodings: [{rid: "foo"}, {rid: "bar"}]});
await doOfferToSendSimulcastAndAnswer(pc1, pc2, ["foo", "bar"]);
assert_equals(pc1.getTransceivers().length, 1);
let encodings = sender.getParameters().encodings;
let rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "bar"]);
// doAnswerToSendSimulcast causes pc2 to barf unless we set the direction to
// sendrecv
pc2.getTransceivers()[0].direction = "sendrecv";
pc2.getTransceivers()[1].direction = "sendrecv";
// Keep the second encoding!
await doOfferToRecvSimulcast(pc2, pc1, ["bar"]);
assert_equals(pc1.getTransceivers().length, 1);
encodings = sender.getParameters().encodings;
rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "bar"]);
await pc1.setRemoteDescription({sdp: "", type: "rollback"});
encodings = sender.getParameters().encodings;
rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "bar"]);
}, 'Rollback of sRD(reoffer) with a single rid results in all previous encodings');
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
const {sender} = pc1.addTransceiver("video", {sendEncodings: [{rid: "foo"}, {rid: "bar"}]});
let {encodings} = sender.getParameters();
await doOfferToSendSimulcastAndAnswer(pc1, pc2, ["bar"]);
assert_equals(pc1.getTransceivers().length, 1);
encodings = sender.getParameters().encodings;
const rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["bar"]);
const scaleDownByValues = encodings.map(({scaleResolutionDownBy}) => scaleResolutionDownBy);
assert_array_equals(scaleDownByValues, [1]);
}, 'sRD(recv simulcast answer) can narrow the simulcast envelope specified by addTransceiver by removing the first encoding');
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
const {sender} = pc1.addTransceiver("video", {sendEncodings: [{rid: "foo"}, {rid: "bar"}]});
let {encodings} = sender.getParameters();
await doOfferToSendSimulcastAndAnswer(pc1, pc2, ["foo", "bar"]);
assert_equals(pc1.getTransceivers().length, 1);
encodings = sender.getParameters().encodings;
let rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "bar"]);
await doOfferToSendSimulcastAndAnswer(pc1, pc2, ["bar"]);
assert_equals(pc1.getTransceivers().length, 1);
encodings = sender.getParameters().encodings;
rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["bar"]);
const scaleDownByValues = encodings.map(({scaleResolutionDownBy}) => scaleResolutionDownBy);
assert_array_equals(scaleDownByValues, [1]);
}, 'sRD(recv simulcast answer) can narrow the simulcast envelope from a previous negotiation by removing the first encoding');
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
const {sender} = pc1.addTransceiver("video", {sendEncodings: [{rid: "foo"}, {rid: "bar"}]});
let {encodings} = sender.getParameters();
await doOfferToSendSimulcastAndAnswer(pc1, pc2, ["foo", "bar"]);
assert_equals(pc1.getTransceivers().length, 1);
encodings = sender.getParameters().encodings;
let rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "bar"]);
// doAnswerToSendSimulcast causes pc2 to barf unless we set the direction to
// sendrecv
pc2.getTransceivers()[0].direction = "sendrecv";
pc2.getTransceivers()[1].direction = "sendrecv";
await doOfferToRecvSimulcast(pc2, pc1, ["bar"]);
assert_equals(pc1.getTransceivers().length, 1);
encodings = sender.getParameters().encodings;
rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "bar"], "[[SendEncodings]] is not updated in have-remote-offer for reoffers");
await doAnswerToSendSimulcast(pc2, pc1);
assert_equals(pc1.getTransceivers().length, 1);
encodings = sender.getParameters().encodings;
rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["bar"]);
const scaleDownByValues = encodings.map(({scaleResolutionDownBy}) => scaleResolutionDownBy);
assert_array_equals(scaleDownByValues, [1]);
}, 'sRD(simulcast offer) can narrow the simulcast envelope from a previous negotiation by removing the first encoding');
</script>

View File

@ -24,56 +24,6 @@ async function queryReceiverStats(pc) {
return inboundStats.map(s => s.framesDecoded);
}
promise_test(async t => {
const rids = [0, 1];
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
await negotiateSimulcastAndWaitForVideo(t, rids, pc1, pc2);
// Deactivate first sender.
const parameters = pc1.getSenders()[0].getParameters();
parameters.encodings[0].active = false;
await pc1.getSenders()[0].setParameters(parameters);
// Assert (almost) no new frames are received on the first encoding.
// Without any action we would expect to have received around 30fps.
await new Promise(resolve => t.step_timeout(resolve, 100)); // Wait a bit.
const initialStats = await queryReceiverStats(pc2);
await new Promise(resolve => t.step_timeout(resolve, 1000)); // Wait more.
const subsequentStats = await queryReceiverStats(pc2);
assert_equals(subsequentStats[0], initialStats[0]);
assert_greater_than(subsequentStats[1], initialStats[1]);
}, 'Simulcast setParameters active=false on first encoding stops sending frames for that encoding');
promise_test(async t => {
const rids = [0, 1];
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
await negotiateSimulcastAndWaitForVideo(t, rids, pc1, pc2);
// Deactivate second sender.
const parameters = pc1.getSenders()[0].getParameters();
parameters.encodings[1].active = false;
await pc1.getSenders()[0].setParameters(parameters);
// Assert (almost) no new frames are received on the second encoding.
// Without any action we would expect to have received around 30fps.
await new Promise(resolve => t.step_timeout(resolve, 100)); // Wait a bit.
const initialStats = await queryReceiverStats(pc2);
await new Promise(resolve => t.step_timeout(resolve, 1000)); // Wait more.
const subsequentStats = await queryReceiverStats(pc2);
assert_equals(subsequentStats[1], initialStats[1]);
assert_greater_than(subsequentStats[0], initialStats[0]);
}, 'Simulcast setParameters active=false on second encoding stops sending frames for that encoding');
promise_test(async t => {
const rids = [0, 1];
const pc1 = new RTCPeerConnection();

View File

@ -1,468 +0,0 @@
<!doctype html>
<meta charset=utf-8>
<title>RTCPeerConnection Simulcast Tests - setParameters/encodings</title>
<meta name="timeout" content="long">
<script src="../third_party/sdp/sdp.js"></script>
<script src="simulcast.js"></script>
<script src="../RTCPeerConnection-helper.js"></script>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="../../mediacapture-streams/permission-helper.js"></script>
<script>
async function queueAWebrtcTask() {
const pc = new RTCPeerConnection();
pc.addTransceiver('audio');
await new Promise(r => pc.onnegotiationneeded = r);
}
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
const {sender} = pc1.addTransceiver("video", {sendEncodings: [{rid: "foo"}, {rid: "bar"}]});
await doOfferToSendSimulcast(pc1, pc2);
await pc2.setLocalDescription();
const simulcastAnswer = midToRid(pc2.localDescription, pc1.localDescription, ["foo"]);
const parameters = sender.getParameters();
parameters.encodings[1].scaleResolutionDownBy = 3.3;
const answerDone = pc1.setRemoteDescription({type: "answer", sdp: simulcastAnswer});
await sender.setParameters(parameters);
await answerDone;
assert_equals(pc1.getTransceivers().length, 1);
const {encodings} = sender.getParameters();
const rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo"]);
}, 'sRD(simulcast answer) can narrow the simulcast envelope when interrupted by a setParameters');
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
const {sender} = pc1.addTransceiver("video", {sendEncodings: [{rid: "foo"}, {rid: "bar"}]});
await doOfferToSendSimulcastAndAnswer(pc1, pc2, ["foo", "bar"]);
assert_equals(pc1.getTransceivers().length, 1);
let encodings = sender.getParameters().encodings;
let rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "bar"]);
const reoffer = await pc2.createOffer();
const simulcastSdp = midToRid(reoffer, pc1.localDescription, ["foo"]);
const parameters = sender.getParameters();
parameters.encodings[1].scaleResolutionDownBy = 3.3;
const reofferDone = pc1.setRemoteDescription({type: "offer", sdp: simulcastSdp});
await sender.setParameters(parameters);
await reofferDone;
await pc1.setLocalDescription();
encodings = sender.getParameters().encodings;
rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo"]);
}, 'sRD(simulcast offer) can narrow the simulcast envelope when interrupted by a setParameters');
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
const {sender} = pc1.addTransceiver("video", {sendEncodings: [{rid: "foo"}, {rid: "bar"}]});
const parameters = sender.getParameters();
parameters.encodings[0].scaleResolutionDownBy = 2.3;
parameters.encodings[1].scaleResolutionDownBy = 3.3;
await sender.setParameters(parameters);
await doOfferToSendSimulcast(pc1, pc2);
await doAnswerToRecvSimulcast(pc1, pc2, []);
assert_equals(pc1.getTransceivers().length, 1);
const encodings = sender.getParameters().encodings;
const rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo"]);
assert_equals(encodings[0].scaleResolutionDownBy, 2.3);
}, 'a simulcast setParameters followed by a sRD(unicast answer) results in keeping the first encoding');
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
const {sender} = pc1.addTransceiver("video", {sendEncodings: [{rid: "foo"}, {rid: "bar"}]});
await doOfferToSendSimulcast(pc1, pc2);
await pc2.setLocalDescription();
const unicastAnswer = midToRid(pc2.localDescription, pc1.localDescription, []);
const parameters = sender.getParameters();
parameters.encodings[0].scaleResolutionDownBy = 2.3;
parameters.encodings[1].scaleResolutionDownBy = 3.3;
const answerDone = pc1.setRemoteDescription({type: "answer", sdp: unicastAnswer});
await sender.setParameters(parameters);
await answerDone;
assert_equals(pc1.getTransceivers().length, 1);
const encodings = sender.getParameters().encodings;
const rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo"]);
assert_equals(encodings[0].scaleResolutionDownBy, 2.3);
}, 'sRD(unicast answer) interrupted by setParameters(simulcast) results in keeping the first encoding');
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
const {sender} = pc1.addTransceiver("video", {sendEncodings: [{rid: "foo"}, {rid: "bar"}]});
await doOfferToSendSimulcastAndAnswer(pc1, pc2, ["foo", "bar"]);
assert_equals(pc1.getTransceivers().length, 1);
let encodings = sender.getParameters().encodings;
let rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "bar"]);
const reoffer = await pc2.createOffer();
const unicastSdp = midToRid(reoffer, pc1.localDescription, []);
const parameters = sender.getParameters();
parameters.encodings[0].scaleResolutionDownBy = 2.3;
parameters.encodings[1].scaleResolutionDownBy = 3.3;
const reofferDone = pc1.setRemoteDescription({type: "offer", sdp: unicastSdp});
await sender.setParameters(parameters);
await reofferDone;
await pc1.setLocalDescription();
encodings = sender.getParameters().encodings;
rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo"]);
assert_equals(encodings[0].scaleResolutionDownBy, 2.3);
}, 'sRD(unicast reoffer) interrupted by setParameters(simulcast) results in keeping the first encoding');
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
const {sender} = pc1.addTransceiver("video", {sendEncodings: [{rid: "foo"}, {rid: "bar"}]});
await doOfferToSendSimulcast(pc1, pc2);
await pc2.setLocalDescription();
const simulcastAnswer = midToRid(pc2.localDescription, pc1.localDescription, ["foo"]);
const parameters = sender.getParameters();
parameters.encodings[0].scaleResolutionDownBy = 3.3;
const answerDone = pc1.setRemoteDescription({type: "answer", sdp: simulcastAnswer});
await sender.setParameters(parameters);
await answerDone;
const {encodings} = sender.getParameters();
assert_equals(encodings.length, 1);
assert_equals(encodings[0].scaleResolutionDownBy, 3.3);
}, 'sRD(simulcast answer) interrupted by a setParameters does not result in losing modifications from the setParameters to the encodings that remain');
const simulcastOffer = `v=0
o=- 3840232462471583827 0 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0
a=msid-semantic: WMS
m=video 9 UDP/TLS/RTP/SAVPF 96
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:Li6+
a=ice-pwd:3C05CTZBRQVmGCAq7hVasHlT
a=ice-options:trickle
a=fingerprint:sha-256 5B:D3:8E:66:0E:7D:D3:F3:8E:E6:80:28:19:FC:55:AD:58:5D:B9:3D:A8:DE:45:4A:E7:87:02:F8:3C:0B:3B:B3
a=setup:actpass
a=mid:0
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=recvonly
a=rtcp-mux
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rid:foo recv
a=rid:bar recv
a=simulcast:recv foo;bar
`;
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const stream = await getNoiseStream({video: true});
t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
const sender = pc1.addTrack(stream.getTracks()[0]);
const parameters = sender.getParameters();
parameters.encodings[0].scaleResolutionDownBy = 3.0;
await sender.setParameters(parameters);
await pc1.setRemoteDescription({type: "offer", sdp: simulcastOffer});
const {encodings} = sender.getParameters();
const rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "bar"]);
assert_equals(encodings[0].scaleResolutionDownBy, 2.0);
assert_equals(encodings[1].scaleResolutionDownBy, 1.0);
}, 'addTrack, then a unicast setParameters, then sRD(simulcast offer) results in simulcast without the settings from setParameters');
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const stream = await getNoiseStream({video: true});
t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
const sender = pc1.addTrack(stream.getTracks()[0]);
const parameters = sender.getParameters();
parameters.encodings[0].scaleResolutionDownBy = 3.0;
const offerDone = pc1.setRemoteDescription({type: "offer", sdp: simulcastOffer});
await sender.setParameters(parameters);
await offerDone;
const {encodings} = sender.getParameters();
const rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "bar"]);
assert_equals(encodings[0].scaleResolutionDownBy, 2.0);
assert_equals(encodings[1].scaleResolutionDownBy, 1.0);
}, 'addTrack, then sRD(simulcast offer) interrupted by a unicast setParameters results in simulcast without the settings from setParameters');
promise_test(async t => {
const pc1 = new RTCPeerConnection();
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
t.add_cleanup(() => pc2.close());
const stream = await getNoiseStream({video: true});
t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
const {sender} = pc1.addTransceiver(stream.getTracks()[0], {sendEncodings: [{rid: "foo"}, {rid: "bar"}]});
await doOfferToSendSimulcastAndAnswer(pc1, pc2, ["foo", "bar"]);
pc2.getTransceivers()[0].direction = "sendrecv";
pc2.getTransceivers()[1].direction = "sendrecv";
await doOfferToRecvSimulcast(pc2, pc1, []);
// Race simulcast setParameters against sLD(unicast reanswer)
const answer = await pc1.createAnswer();
const aTask = queueAWebrtcTask();
// This also queues a task to clear [[LastReturnedParameters]]
const parameters = sender.getParameters();
// This might or might not queue a task right away (it might do some
// microtask stuff first), but it doesn't really matter.
const sLDDone = pc1.setLocalDescription(answer);
await aTask;
// Task queue should now have the task that clears
// [[LastReturnedParameters]], _then_ the success task for sLD.
// setParameters should succeed because [[LastReturnedParameters]] has not
// yet been cleared, and the steps in the success task for sLD have not run
// either.
await sender.setParameters(parameters);
await sLDDone;
assert_equals(pc1.getTransceivers().length, 1);
const {encodings} = sender.getParameters();
const rids = encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo"]);
}, 'getParameters, then sLD(unicast answer) interrupted by a simulcast setParameters results in unicast');
promise_test(async t => {
const pc1 = new RTCPeerConnection();
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
t.add_cleanup(() => pc2.close());
const stream = await getNoiseStream({video: true});
t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
const {sender} = pc1.addTransceiver(stream.getTracks()[0], {sendEncodings: [{rid: "foo"}, {rid: "bar"}]});
await doOfferToSendSimulcastAndAnswer(pc1, pc2, ["foo", "bar"]);
pc2.getTransceivers()[0].direction = "sendrecv";
pc2.getTransceivers()[1].direction = "sendrecv";
await doOfferToRecvSimulcast(pc2, pc1, []);
const answer = await pc1.createAnswer();
// The timing on this is very difficult. We want to ensure that our
// getParameters call happens after the initial steps in sLD, but
// before the queued task that sLD runs when it completes.
const aTask = queueAWebrtcTask();
const sLDDone = pc1.setLocalDescription(answer);
// We now have a queued task (aTask). We might also have the success task for
// sLD, but maybe not. Allowing aTask to finish gives us our best chance that
// the success task for sLD is queued, but not run yet.
await aTask;
const parameters = sender.getParameters();
// Hopefully we now have the success task for sLD, followed by the
// success task for getParameters.
await sLDDone;
// Success task for getParameters should not have run yet.
await promise_rejects_dom(t, 'InvalidStateError', sender.setParameters(parameters));
},'Success task for setLocalDescription(answer) clears [[LastReturnedParameters]]');
promise_test(async t => {
const pc1 = new RTCPeerConnection();
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
t.add_cleanup(() => pc2.close());
const stream = await getNoiseStream({video: true});
t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
const {sender} = pc1.addTransceiver(stream.getTracks()[0], {sendEncodings: [{rid: "foo"}, {rid: "bar"}]});
await doOfferToSendSimulcastAndAnswer(pc1, pc2, ["foo", "bar"]);
pc2.getTransceivers()[0].direction = "sendrecv";
pc2.getTransceivers()[1].direction = "sendrecv";
await pc2.setLocalDescription();
const simulcastOffer = midToRid(
pc2.localDescription,
pc1.localDescription,
[]
);
// The timing on this is very difficult. We need to ensure that our
// getParameters call happens after the initial steps in sRD, but
// before the queued task that sRD runs when it completes.
const aTask = queueAWebrtcTask();
const sRDDone = pc1.setRemoteDescription({ type: "offer", sdp: simulcastOffer });
await aTask;
const parameters = sender.getParameters();
await sRDDone;
await promise_rejects_dom(t, 'InvalidStateError', sender.setParameters(parameters));
},'Success task for setRemoteDescription(offer) clears [[LastReturnedParameters]]');
promise_test(async t => {
const pc1 = new RTCPeerConnection();
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
t.add_cleanup(() => pc2.close());
const stream = await getNoiseStream({video: true});
t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
const {sender} = pc1.addTransceiver(stream.getTracks()[0], {sendEncodings: [{rid: "foo"}, {rid: "bar"}]});
await doOfferToSendSimulcastAndAnswer(pc1, pc2, ["foo", "bar"]);
pc2.getTransceivers()[0].direction = "sendrecv";
pc2.getTransceivers()[1].direction = "sendrecv";
await doOfferToSendSimulcast(pc1, pc2);
await pc2.setLocalDescription();
const simulcastAnswer = midToRid(
pc2.localDescription,
pc1.localDescription,
[]
);
// The timing on this is very difficult. We need to ensure that our
// getParameters call happens after the initial steps in sRD, but
// before the queued task that sRD runs when it completes.
const aTask = queueAWebrtcTask();
const sRDDone = pc1.setRemoteDescription({ type: "answer", sdp: simulcastAnswer });
await aTask;
const parameters = sender.getParameters();
await sRDDone;
await promise_rejects_dom(t, 'InvalidStateError', sender.setParameters(parameters));
},'Success task for setRemoteDescription(answer) clears [[LastReturnedParameters]]');
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
const stream = await getNoiseStream({video: true});
t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
const sender = pc1.addTrack(stream.getTracks()[0]);
pc2.addTrack(stream.getTracks()[0]);
await doOfferToRecvSimulcast(pc2, pc1, ["foo", "bar"]);
let parameters = sender.getParameters();
let rids = parameters.encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "bar"]);
parameters.encodings[0].scaleResolutionDownBy = 3;
parameters.encodings[1].scaleResolutionDownBy = 5;
await sender.setParameters(parameters);
await pc1.setRemoteDescription({sdp: "", type: "rollback"});
parameters = sender.getParameters();
rids = parameters.encodings.map(({rid}) => rid);
assert_array_equals(rids, [undefined]);
assert_equals(parameters.encodings[0].scaleResolutionDownBy, 1);
}, 'addTrack, then rollback of sRD(simulcast offer), brings us back to having a single encoding without any previously set parameters');
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
const stream = await getNoiseStream({video: true});
t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
const {sender} = pc1.addTransceiver(stream.getTracks()[0], {sendEncodings: [{rid: "foo"}, {rid: "bar"}]});
await doOfferToSendSimulcastAndAnswer(pc1, pc2, ["foo", "bar"]);
let parameters = sender.getParameters();
let rids = parameters.encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "bar"]);
parameters.encodings[0].scaleResolutionDownBy = 3;
parameters.encodings[1].scaleResolutionDownBy = 5;
await sender.setParameters(parameters);
await doOfferToRecvSimulcast(pc2, pc1, []);
parameters = sender.getParameters();
rids = parameters.encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "bar"]);
await pc1.setRemoteDescription({sdp: "", type: "rollback"});
parameters = sender.getParameters();
rids = parameters.encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "bar"]);
assert_equals(parameters.encodings[0].scaleResolutionDownBy, 3);
assert_equals(parameters.encodings[1].scaleResolutionDownBy, 5);
}, 'rollback of a remote offer that disabled a previously negotiated simulcast should restore simulcast along with any previously set parameters');
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
const stream = await getNoiseStream({video: true});
t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
const sender = pc1.addTrack(stream.getTracks()[0]);
pc2.addTrack(stream.getTracks()[0]);
await doOfferToRecvSimulcast(pc2, pc1, ["foo", "bar"]);
const aTask = queueAWebrtcTask();
let parameters = sender.getParameters();
let rids = parameters.encodings.map(({rid}) => rid);
assert_array_equals(rids, ["foo", "bar"]);
parameters.encodings[0].scaleResolutionDownBy = 3;
parameters.encodings[1].scaleResolutionDownBy = 5;
const rollbackDone = pc1.setRemoteDescription({sdp: "", type: "rollback"});
await aTask;
await sender.setParameters(parameters);
await rollbackDone;
parameters = sender.getParameters();
rids = parameters.encodings.map(({rid}) => rid);
assert_array_equals(rids, [undefined]);
assert_equals(parameters.encodings[0].scaleResolutionDownBy, 1);
}, 'rollback of sRD(simulcast offer) interrupted by setParameters(simulcast) brings us back to having a single encoding without any previously set parameters');
</script>

View File

@ -7,192 +7,73 @@
* which allows receiving the different spatial resolutions on separate
* m-lines and tracks.
*/
const ridExtensions = [
"urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id",
"urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id",
const extensionsToFilter = [
'urn:ietf:params:rtp-hdrext:sdes:mid',
'urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id',
'urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id',
];
function ridToMid(description, rids) {
const sections = SDPUtils.splitSections(description.sdp);
function swapRidAndMidExtensionsInSimulcastOffer(offer, rids) {
const sections = SDPUtils.splitSections(offer.sdp);
const dtls = SDPUtils.getDtlsParameters(sections[1], sections[0]);
const ice = SDPUtils.getIceParameters(sections[1], sections[0]);
const rtpParameters = SDPUtils.parseRtpParameters(sections[1]);
const setupValue = description.sdp.match(/a=setup:(.*)/)[1];
const directionValue =
description.sdp.match(/a=sendrecv|a=sendonly|a=recvonly|a=inactive/) ||
"a=sendrecv";
const mline = SDPUtils.parseMLine(sections[1]);
// Skip mid extension; we are replacing it with the rid extmap
rtpParameters.headerExtensions = rtpParameters.headerExtensions.filter(
ext => ext.uri != "urn:ietf:params:rtp-hdrext:sdes:mid"
);
for (const ext of rtpParameters.headerExtensions) {
if (ext.uri == "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id") {
ext.uri = "urn:ietf:params:rtp-hdrext:sdes:mid";
}
}
// The gist of this hack is that rid and mid have the same wire format.
const rid = rtpParameters.headerExtensions.find(ext => ext.uri === 'urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id');
rtpParameters.headerExtensions = rtpParameters.headerExtensions.filter(ext => {
return !extensionsToFilter.includes(ext.uri);
});
// This tells the other side that the RID packets are actually mids.
rtpParameters.headerExtensions.push({id: rid.id, uri: 'urn:ietf:params:rtp-hdrext:sdes:mid', direction: 'sendrecv'});
// Filter rtx as we have no way to (re)interpret rrid.
// Not doing this makes probing use RTX, it's not understood and ramp-up is slower.
rtpParameters.codecs = rtpParameters.codecs.filter(c => c.name.toUpperCase() !== 'RTX');
if (!rids) {
rids = Array.from(description.sdp.matchAll(/a=rid:(.*) send/g)).map(r => r[1]);
}
let sdp = SDPUtils.writeSessionBoilerplate() +
SDPUtils.writeDtlsParameters(dtls, setupValue) +
SDPUtils.writeDtlsParameters(dtls, 'actpass') +
SDPUtils.writeIceParameters(ice) +
'a=group:BUNDLE ' + rids.join(' ') + '\r\n';
const baseRtpDescription = SDPUtils.writeRtpDescription(mline.kind, rtpParameters);
for (const rid of rids) {
const baseRtpDescription = SDPUtils.writeRtpDescription('video', rtpParameters);
rids.forEach(rid => {
sdp += baseRtpDescription +
'a=mid:' + rid + '\r\n' +
'a=msid:rid-' + rid + ' rid-' + rid + '\r\n';
sdp += directionValue + "\r\n";
}
return sdp;
}
function midToRid(description, localDescription, rids) {
const sections = SDPUtils.splitSections(description.sdp);
const dtls = SDPUtils.getDtlsParameters(sections[1], sections[0]);
const ice = SDPUtils.getIceParameters(sections[1], sections[0]);
const rtpParameters = SDPUtils.parseRtpParameters(sections[1]);
const setupValue = description.sdp.match(/a=setup:(.*)/)[1];
const directionValue =
description.sdp.match(/a=sendrecv|a=sendonly|a=recvonly|a=inactive/) ||
"a=sendrecv";
const mline = SDPUtils.parseMLine(sections[1]);
// Skip rid extensions; we are replacing them with the mid extmap
rtpParameters.headerExtensions = rtpParameters.headerExtensions.filter(
ext => !ridExtensions.includes(ext.uri)
);
for (const ext of rtpParameters.headerExtensions) {
if (ext.uri == "urn:ietf:params:rtp-hdrext:sdes:mid") {
ext.uri = "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id";
}
}
const localMid = localDescription ? SDPUtils.getMid(SDPUtils.splitSections(localDescription.sdp)[1]) : "0";
if (!rids) {
rids = [];
for (let i = 1; i < sections.length; i++) {
rids.push(SDPUtils.getMid(sections[i]));
}
}
let sdp = SDPUtils.writeSessionBoilerplate() +
SDPUtils.writeDtlsParameters(dtls, setupValue) +
SDPUtils.writeIceParameters(ice) +
'a=group:BUNDLE ' + localMid + '\r\n';
sdp += SDPUtils.writeRtpDescription(mline.kind, rtpParameters);
// Although we are converting mids to rids, we still need a mid.
// The first one will be consistent with trickle ICE candidates.
sdp += 'a=mid:' + localMid + '\r\n';
sdp += directionValue + "\r\n";
for (const rid of rids) {
const stringrid = String(rid); // allow integers
const choices = stringrid.split(",");
choices.forEach(choice => {
sdp += 'a=rid:' + choice + ' recv\r\n';
});
}
if (rids.length) {
sdp += 'a=simulcast:recv ' + rids.join(';') + '\r\n';
}
return sdp;
}
async function doOfferToSendSimulcast(offerer, answerer) {
await offerer.setLocalDescription();
// Is this a renegotiation? If so, we cannot remove (or reorder!) any mids,
// even if some rids have been removed or reordered.
let mids = [];
if (answerer.localDescription) {
// Renegotiation. Mids must be the same as before, because renegotiation
// can never remove or reorder mids, nor can it expand the simulcast
// envelope.
mids = [...answerer.localDescription.sdp.matchAll(/a=mid:(.*)/g)].map(
e => e[1]
);
} else {
// First negotiation; the mids will be exactly the same as the rids
const simulcastAttr = offerer.localDescription.sdp.match(
/a=simulcast:send (.*)/
);
if (simulcastAttr) {
mids = simulcastAttr[1].split(";");
}
}
const nonSimulcastOffer = ridToMid(offerer.localDescription, mids);
await answerer.setRemoteDescription({
type: "offer",
sdp: nonSimulcastOffer,
});
}
async function doAnswerToRecvSimulcast(offerer, answerer, rids) {
await answerer.setLocalDescription();
const simulcastAnswer = midToRid(
answerer.localDescription,
offerer.localDescription,
rids
);
await offerer.setRemoteDescription({ type: "answer", sdp: simulcastAnswer });
}
async function doOfferToRecvSimulcast(offerer, answerer, rids) {
await offerer.setLocalDescription();
const simulcastOffer = midToRid(
offerer.localDescription,
answerer.localDescription,
rids
);
await answerer.setRemoteDescription({ type: "offer", sdp: simulcastOffer });
}
async function doAnswerToSendSimulcast(offerer, answerer) {
await answerer.setLocalDescription();
// See which mids the offerer had; it will barf if we remove or reorder them
const mids = [...offerer.localDescription.sdp.matchAll(/a=mid:(.*)/g)].map(
e => e[1]
);
const nonSimulcastAnswer = ridToMid(answerer.localDescription, mids);
await offerer.setRemoteDescription({
type: "answer",
sdp: nonSimulcastAnswer,
});
}
async function doOfferToSendSimulcastAndAnswer(offerer, answerer, rids) {
await doOfferToSendSimulcast(offerer, answerer);
await doAnswerToRecvSimulcast(offerer, answerer, rids);
}
async function doOfferToRecvSimulcastAndAnswer(offerer, answerer, rids) {
await doOfferToRecvSimulcast(offerer, answerer, rids);
await doAnswerToSendSimulcast(offerer, answerer);
}
function swapRidAndMidExtensionsInSimulcastOffer(offer, rids) {
return ridToMid(offer, rids);
return sdp;
}
function swapRidAndMidExtensionsInSimulcastAnswer(answer, localDescription, rids) {
return midToRid(answer, localDescription, rids);
const sections = SDPUtils.splitSections(answer.sdp);
const dtls = SDPUtils.getDtlsParameters(sections[1], sections[0]);
const ice = SDPUtils.getIceParameters(sections[1], sections[0]);
const rtpParameters = SDPUtils.parseRtpParameters(sections[1]);
rtpParameters.headerExtensions = rtpParameters.headerExtensions.filter(ext => {
return !extensionsToFilter.includes(ext.uri);
});
const localMid = SDPUtils.getMid(SDPUtils.splitSections(localDescription.sdp)[1]);
let sdp = SDPUtils.writeSessionBoilerplate() +
SDPUtils.writeDtlsParameters(dtls, 'active') +
SDPUtils.writeIceParameters(ice) +
'a=group:BUNDLE ' + localMid + '\r\n';
sdp += SDPUtils.writeRtpDescription('video', rtpParameters);
sdp += 'a=mid:' + localMid + '\r\n';
rids.forEach(rid => {
sdp += 'a=rid:' + rid + ' recv\r\n';
});
sdp += 'a=simulcast:recv ' + rids.join(';') + '\r\n';
// Re-add headerextensions we filtered.
const headerExtensions = SDPUtils.parseRtpParameters(SDPUtils.splitSections(localDescription.sdp)[1]).headerExtensions;
headerExtensions.forEach(ext => {
if (extensionsToFilter.includes(ext.uri)) {
sdp += 'a=extmap:' + ext.id + ' ' + ext.uri + '\r\n';
}
});
return sdp;
}
async function negotiateSimulcastAndWaitForVideo(t, rids, pc1, pc2, codec) {

View File

@ -17,7 +17,6 @@
gecko_metrics = [
"browser/base/content/metrics.yaml",
"dom/media/metrics.yaml",
"dom/media/webrtc/metrics.yaml",
"dom/metrics.yaml",
"gfx/metrics.yaml",
"netwerk/metrics.yaml",