mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-07 18:04:46 +00:00
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:
parent
28f5b1d868
commit
74a43f86ea
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 &&
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
|
@ -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
|
@ -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));
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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>
|
@ -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>
|
@ -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") {
|
||||
|
@ -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>
|
@ -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>
|
||||
|
@ -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];
|
||||
|
@ -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>
|
@ -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>
|
@ -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
|
||||
|
@ -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>
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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>
|
@ -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>
|
@ -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();
|
||||
|
@ -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>
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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>
|
@ -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>
|
@ -132,7 +132,6 @@ interface RTCPeerConnection : EventTarget {
|
||||
MediaStream... streams);
|
||||
undefined removeTrack(RTCRtpSender sender);
|
||||
|
||||
[Throws]
|
||||
RTCRtpTransceiver addTransceiver((MediaStreamTrack or DOMString) trackOrKind,
|
||||
optional RTCRtpTransceiverInit init = {});
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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]
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -59,8 +59,6 @@ function androidStartup() {
|
||||
|
||||
windowTracker.init();
|
||||
}
|
||||
|
||||
Services.fog.initializeFOG();
|
||||
}
|
||||
|
||||
// ///// Desktop ///////
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -0,0 +1,3 @@
|
||||
[RTCRtpParameters-encodings.html]
|
||||
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1531458
|
||||
|
@ -0,0 +1,3 @@
|
||||
[RTCRtpParameters-transactionId.html]
|
||||
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1531458
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -0,0 +1,5 @@
|
||||
[basic.https.html]
|
||||
expected:
|
||||
if (os == "android") and fission: [TIMEOUT, OK]
|
||||
[Basic simulcast setup with two spatial layers]
|
||||
expected: FAIL
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
|
@ -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');
|
||||
|
@ -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');
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
@ -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();
|
||||
|
@ -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>
|
@ -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) {
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user