1896 lines
70 KiB
C++
1896 lines
70 KiB
C++
/* 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/. */
|
|
|
|
#include "RTCRtpSender.h"
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <vector>
|
|
#include <string>
|
|
#include <algorithm>
|
|
#include <utility>
|
|
#include <iterator>
|
|
#include <set>
|
|
#include <sstream>
|
|
|
|
#include "system_wrappers/include/clock.h"
|
|
#include "call/call.h"
|
|
#include "api/rtp_parameters.h"
|
|
#include "api/units/time_delta.h"
|
|
#include "api/units/timestamp.h"
|
|
#include "api/video_codecs/video_codec.h"
|
|
#include "api/video/video_codec_constants.h"
|
|
#include "call/audio_send_stream.h"
|
|
#include "call/video_send_stream.h"
|
|
#include "modules/rtp_rtcp/include/report_block_data.h"
|
|
#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
|
|
|
|
#include "nsPIDOMWindow.h"
|
|
#include "nsString.h"
|
|
#include "MainThreadUtils.h"
|
|
#include "nsCOMPtr.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsCycleCollectionParticipant.h"
|
|
#include "nsDebug.h"
|
|
#include "nsISupports.h"
|
|
#include "nsLiteralString.h"
|
|
#include "nsStringFwd.h"
|
|
#include "nsTArray.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "nsWrapperCache.h"
|
|
#include "mozilla/AbstractThread.h"
|
|
#include "mozilla/AlreadyAddRefed.h"
|
|
#include "mozilla/Assertions.h"
|
|
#include "mozilla/Attributes.h"
|
|
#include "mozilla/fallible.h"
|
|
#include "mozilla/Logging.h"
|
|
#include "mozilla/mozalloc_oom.h"
|
|
#include "mozilla/MozPromise.h"
|
|
#include "mozilla/OwningNonNull.h"
|
|
#include "mozilla/RefPtr.h"
|
|
#include "mozilla/StateWatching.h"
|
|
#include "mozilla/UniquePtr.h"
|
|
#include "mozilla/Unused.h"
|
|
#include "mozilla/StateMirroring.h"
|
|
#include "mozilla/Maybe.h"
|
|
#include "mozilla/dom/MediaStreamTrack.h"
|
|
#include "mozilla/dom/Promise.h"
|
|
#include "mozilla/dom/RTCRtpScriptTransform.h"
|
|
#include "mozilla/dom/VideoStreamTrack.h"
|
|
#include "mozilla/dom/RTCRtpSenderBinding.h"
|
|
#include "mozilla/dom/MediaStreamTrackBinding.h"
|
|
#include "mozilla/dom/Nullable.h"
|
|
#include "mozilla/dom/RTCRtpParametersBinding.h"
|
|
#include "mozilla/dom/RTCStatsReportBinding.h"
|
|
#include "mozilla/glean/DomMediaWebrtcMetrics.h"
|
|
#include "js/RootingAPI.h"
|
|
#include "jsep/JsepTransceiver.h"
|
|
#include "RTCStatsReport.h"
|
|
#include "RTCRtpTransceiver.h"
|
|
#include "PeerConnectionImpl.h"
|
|
#include "libwebrtcglue/CodecConfig.h"
|
|
#include "libwebrtcglue/MediaConduitControl.h"
|
|
#include "libwebrtcglue/MediaConduitInterface.h"
|
|
#include "sdp/SdpAttribute.h"
|
|
#include "sdp/SdpEnum.h"
|
|
|
|
namespace mozilla::dom {
|
|
|
|
LazyLogModule gSenderLog("RTCRtpSender");
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(RTCRtpSender)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(RTCRtpSender)
|
|
tmp->Unlink();
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(RTCRtpSender)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow, mPc, mSenderTrack, mTransceiver,
|
|
mStreams, mTransform, mDtmf)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCRtpSender)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(RTCRtpSender)
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCRtpSender)
|
|
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
#define INIT_CANONICAL(name, val) \
|
|
name(AbstractThread::MainThread(), val, "RTCRtpSender::" #name " (Canonical)")
|
|
|
|
RTCRtpSender::RTCRtpSender(nsPIDOMWindowInner* aWindow, PeerConnectionImpl* aPc,
|
|
MediaTransportHandler* aTransportHandler,
|
|
AbstractThread* aCallThread,
|
|
nsISerialEventTarget* aStsThread,
|
|
MediaSessionConduit* aConduit,
|
|
dom::MediaStreamTrack* aTrack,
|
|
const Sequence<RTCRtpEncodingParameters>& aEncodings,
|
|
RTCRtpTransceiver* aTransceiver)
|
|
: mWatchManager(this, AbstractThread::MainThread()),
|
|
mWindow(aWindow),
|
|
mPc(aPc),
|
|
mSenderTrack(aTrack),
|
|
mTransportHandler(aTransportHandler),
|
|
mTransceiver(aTransceiver),
|
|
INIT_CANONICAL(mSsrcs, Ssrcs()),
|
|
INIT_CANONICAL(mVideoRtxSsrcs, Ssrcs()),
|
|
INIT_CANONICAL(mLocalRtpExtensions, RtpExtList()),
|
|
INIT_CANONICAL(mAudioCodec, Nothing()),
|
|
INIT_CANONICAL(mVideoCodec, Nothing()),
|
|
INIT_CANONICAL(mVideoRtpRtcpConfig, Nothing()),
|
|
INIT_CANONICAL(mVideoCodecMode, webrtc::VideoCodecMode::kRealtimeVideo),
|
|
INIT_CANONICAL(mCname, std::string()),
|
|
INIT_CANONICAL(mTransmitting, false),
|
|
INIT_CANONICAL(mFrameTransformerProxy, nullptr),
|
|
INIT_CANONICAL(mVideoDegradationPreference,
|
|
webrtc::DegradationPreference::DISABLED) {
|
|
mPipeline = MediaPipelineTransmit::Create(
|
|
mPc->GetHandle(), aTransportHandler, aCallThread, aStsThread,
|
|
aConduit->type() == MediaSessionConduit::VIDEO, aConduit);
|
|
mPipeline->InitControl(this);
|
|
|
|
if (aConduit->type() == MediaSessionConduit::AUDIO) {
|
|
mDtmf = new RTCDTMFSender(aWindow, mTransceiver);
|
|
}
|
|
mPipeline->SetTrack(mSenderTrack);
|
|
|
|
mozilla::glean::rtcrtpsender::count.Add(1);
|
|
|
|
if (mPc->ShouldAllowOldSetParameters()) {
|
|
mAllowOldSetParameters = true;
|
|
mozilla::glean::rtcrtpsender::count_setparameters_compat.Add(1);
|
|
}
|
|
|
|
if (aEncodings.Length()) {
|
|
// This sender was created by addTransceiver with sendEncodings.
|
|
mParameters.mEncodings = aEncodings;
|
|
mSimulcastEnvelopeSet = true;
|
|
mozilla::glean::rtcrtpsender::used_sendencodings.AddToNumerator(1);
|
|
} else {
|
|
// This sender was created by addTrack, sRD(offer), or addTransceiver
|
|
// without sendEncodings.
|
|
RTCRtpEncodingParameters defaultEncoding;
|
|
defaultEncoding.mActive = true;
|
|
if (aConduit->type() == MediaSessionConduit::VIDEO) {
|
|
defaultEncoding.mScaleResolutionDownBy.Construct(1.0f);
|
|
}
|
|
Unused << mParameters.mEncodings.AppendElement(defaultEncoding, fallible);
|
|
UpdateRestorableEncodings(mParameters.mEncodings);
|
|
MaybeGetJsepRids();
|
|
}
|
|
|
|
mParameters.mCodecs.Construct();
|
|
|
|
if (mDtmf) {
|
|
mWatchManager.Watch(mTransmitting, &RTCRtpSender::UpdateDtmfSender);
|
|
}
|
|
}
|
|
|
|
#undef INIT_CANONICAL
|
|
|
|
RTCRtpSender::~RTCRtpSender() = default;
|
|
|
|
JSObject* RTCRtpSender::WrapObject(JSContext* aCx,
|
|
JS::Handle<JSObject*> aGivenProto) {
|
|
return RTCRtpSender_Binding::Wrap(aCx, this, aGivenProto);
|
|
}
|
|
|
|
RTCDtlsTransport* RTCRtpSender::GetTransport() const {
|
|
if (!mTransceiver) {
|
|
return nullptr;
|
|
}
|
|
return mTransceiver->GetDtlsTransport();
|
|
}
|
|
|
|
RTCDTMFSender* RTCRtpSender::GetDtmf() const { return mDtmf; }
|
|
|
|
already_AddRefed<Promise> RTCRtpSender::GetStats(ErrorResult& aError) {
|
|
RefPtr<Promise> promise = MakePromise(aError);
|
|
if (aError.Failed()) {
|
|
return nullptr;
|
|
}
|
|
if (NS_WARN_IF(!mPipeline)) {
|
|
// TODO(bug 1056433): When we stop nulling this out when the PC is closed
|
|
// (or when the transceiver is stopped), we can remove this code. We
|
|
// resolve instead of reject in order to make this eventual change in
|
|
// behavior a little smaller.
|
|
promise->MaybeResolve(new RTCStatsReport(mWindow));
|
|
return promise.forget();
|
|
}
|
|
|
|
mTransceiver->ChainToDomPromiseWithCodecStats(GetStatsInternal(), promise);
|
|
return promise.forget();
|
|
}
|
|
|
|
nsTArray<RefPtr<dom::RTCStatsPromise>> RTCRtpSender::GetStatsInternal(
|
|
bool aSkipIceStats) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
nsTArray<RefPtr<RTCStatsPromise>> promises(2);
|
|
if (!mPipeline) {
|
|
return promises;
|
|
}
|
|
|
|
nsAutoString trackName;
|
|
if (auto track = mPipeline->GetTrack()) {
|
|
track->GetId(trackName);
|
|
}
|
|
|
|
std::string mid = mTransceiver->GetMidAscii();
|
|
std::map<uint32_t, std::string> videoSsrcToRidMap;
|
|
const auto encodings = mVideoCodec.Ref().andThen(
|
|
[](const auto& aCodec) { return SomeRef(aCodec.mEncodings); });
|
|
if (encodings && !encodings->empty() && encodings->front().rid != "") {
|
|
for (size_t i = 0; i < std::min(mSsrcs.Ref().size(), encodings->size());
|
|
++i) {
|
|
videoSsrcToRidMap.insert({mSsrcs.Ref()[i], (*encodings)[i].rid});
|
|
}
|
|
}
|
|
|
|
{
|
|
// Add bandwidth estimation stats
|
|
promises.AppendElement(InvokeAsync(
|
|
mPipeline->mCallThread, __func__,
|
|
[conduit = mPipeline->mConduit, trackName]() mutable {
|
|
auto report = MakeUnique<dom::RTCStatsCollection>();
|
|
Maybe<webrtc::Call::Stats> stats = conduit->GetCallStats();
|
|
stats.apply([&](const auto aStats) {
|
|
dom::RTCBandwidthEstimationInternal bw;
|
|
bw.mTrackIdentifier = trackName;
|
|
bw.mSendBandwidthBps.Construct(aStats.send_bandwidth_bps / 8);
|
|
bw.mMaxPaddingBps.Construct(aStats.max_padding_bitrate_bps / 8);
|
|
bw.mReceiveBandwidthBps.Construct(aStats.recv_bandwidth_bps / 8);
|
|
bw.mPacerDelayMs.Construct(aStats.pacer_delay_ms);
|
|
if (aStats.rtt_ms >= 0) {
|
|
bw.mRttMs.Construct(aStats.rtt_ms);
|
|
}
|
|
if (!report->mBandwidthEstimations.AppendElement(std::move(bw),
|
|
fallible)) {
|
|
mozalloc_handle_oom(0);
|
|
}
|
|
});
|
|
return RTCStatsPromise::CreateAndResolve(std::move(report), __func__);
|
|
}));
|
|
}
|
|
|
|
promises.AppendElement(InvokeAsync(
|
|
mPipeline->mCallThread, __func__,
|
|
[pipeline = mPipeline, trackName, mid = std::move(mid),
|
|
videoSsrcToRidMap = std::move(videoSsrcToRidMap)] {
|
|
auto report = MakeUnique<dom::RTCStatsCollection>();
|
|
auto asAudio = pipeline->mConduit->AsAudioSessionConduit();
|
|
auto asVideo = pipeline->mConduit->AsVideoSessionConduit();
|
|
|
|
nsString kind = asVideo.isNothing() ? u"audio"_ns : u"video"_ns;
|
|
nsString idstr = kind + u"_"_ns;
|
|
idstr.AppendInt(static_cast<uint32_t>(pipeline->Level()));
|
|
|
|
for (uint32_t ssrc : pipeline->mConduit->GetLocalSSRCs()) {
|
|
nsString localId = u"outbound_rtp_"_ns + idstr + u"_"_ns;
|
|
localId.AppendInt(ssrc);
|
|
nsString remoteId;
|
|
Maybe<uint16_t> base_seq =
|
|
pipeline->mConduit->RtpSendBaseSeqFor(ssrc);
|
|
|
|
auto constructCommonRemoteInboundRtpStats =
|
|
[&](RTCRemoteInboundRtpStreamStats& aRemote,
|
|
const webrtc::ReportBlockData& aRtcpData) {
|
|
remoteId = u"outbound_rtcp_"_ns + idstr + u"_"_ns;
|
|
remoteId.AppendInt(ssrc);
|
|
aRemote.mTimestamp.Construct(
|
|
RTCStatsTimestamp::FromNtp(
|
|
pipeline->GetTimestampMaker(),
|
|
webrtc::Timestamp::Micros(
|
|
aRtcpData.report_block_timestamp_utc().us()) +
|
|
webrtc::TimeDelta::Seconds(webrtc::kNtpJan1970))
|
|
.ToDom());
|
|
aRemote.mId.Construct(remoteId);
|
|
aRemote.mType.Construct(RTCStatsType::Remote_inbound_rtp);
|
|
aRemote.mSsrc = ssrc;
|
|
aRemote.mKind = kind;
|
|
aRemote.mMediaType.Construct(
|
|
kind); // mediaType is the old name for kind.
|
|
aRemote.mLocalId.Construct(localId);
|
|
if (base_seq) {
|
|
if (aRtcpData.extended_highest_sequence_number() <
|
|
*base_seq) {
|
|
aRemote.mPacketsReceived.Construct(0);
|
|
} else {
|
|
aRemote.mPacketsReceived.Construct(
|
|
aRtcpData.extended_highest_sequence_number() -
|
|
aRtcpData.cumulative_lost() - *base_seq + 1);
|
|
}
|
|
}
|
|
};
|
|
|
|
auto constructCommonOutboundRtpStats =
|
|
[&](RTCOutboundRtpStreamStats& aLocal) {
|
|
aLocal.mSsrc = ssrc;
|
|
aLocal.mTimestamp.Construct(
|
|
pipeline->GetTimestampMaker().GetNow().ToDom());
|
|
aLocal.mId.Construct(localId);
|
|
aLocal.mType.Construct(RTCStatsType::Outbound_rtp);
|
|
aLocal.mKind = kind;
|
|
aLocal.mMediaType.Construct(
|
|
kind); // mediaType is the old name for kind.
|
|
if (remoteId.Length()) {
|
|
aLocal.mRemoteId.Construct(remoteId);
|
|
}
|
|
if (!mid.empty()) {
|
|
aLocal.mMid.Construct(NS_ConvertUTF8toUTF16(mid).get());
|
|
}
|
|
};
|
|
|
|
auto constructCommonMediaSourceStats =
|
|
[&](RTCMediaSourceStats& aStats) {
|
|
nsString id = u"mediasource_"_ns + idstr + trackName;
|
|
aStats.mTimestamp.Construct(
|
|
pipeline->GetTimestampMaker().GetNow().ToDom());
|
|
aStats.mId.Construct(id);
|
|
aStats.mType.Construct(RTCStatsType::Media_source);
|
|
aStats.mTrackIdentifier = trackName;
|
|
aStats.mKind = kind;
|
|
};
|
|
|
|
asAudio.apply([&](auto& aConduit) {
|
|
Maybe<webrtc::AudioSendStream::Stats> audioStats =
|
|
aConduit->GetSenderStats();
|
|
if (audioStats.isNothing()) {
|
|
return;
|
|
}
|
|
|
|
if (audioStats->packets_sent == 0) {
|
|
// By spec: "The lifetime of all RTP monitored objects starts
|
|
// when the RTP stream is first used: When the first RTP packet
|
|
// is sent or received on the SSRC it represents"
|
|
return;
|
|
}
|
|
|
|
// First, fill in remote stat with rtcp receiver data, if present.
|
|
// ReceiverReports have less information than SenderReports, so fill
|
|
// in what we can.
|
|
Maybe<webrtc::ReportBlockData> reportBlockData;
|
|
{
|
|
if (const auto remoteSsrc = aConduit->GetRemoteSSRC();
|
|
remoteSsrc) {
|
|
for (auto& data : audioStats->report_block_datas) {
|
|
if (data.source_ssrc() == ssrc &&
|
|
data.sender_ssrc() == *remoteSsrc) {
|
|
reportBlockData.emplace(data);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
reportBlockData.apply([&](auto& aReportBlockData) {
|
|
RTCRemoteInboundRtpStreamStats remote;
|
|
constructCommonRemoteInboundRtpStats(remote, aReportBlockData);
|
|
if (audioStats->jitter_ms >= 0) {
|
|
remote.mJitter.Construct(audioStats->jitter_ms / 1000.0);
|
|
}
|
|
if (audioStats->packets_lost >= 0) {
|
|
remote.mPacketsLost.Construct(audioStats->packets_lost);
|
|
}
|
|
if (audioStats->rtt_ms >= 0) {
|
|
remote.mRoundTripTime.Construct(
|
|
static_cast<double>(audioStats->rtt_ms) / 1000.0);
|
|
}
|
|
remote.mFractionLost.Construct(audioStats->fraction_lost);
|
|
remote.mTotalRoundTripTime.Construct(
|
|
double(aReportBlockData.sum_rtts().ms()) / 1000);
|
|
remote.mRoundTripTimeMeasurements.Construct(
|
|
aReportBlockData.num_rtts());
|
|
if (!report->mRemoteInboundRtpStreamStats.AppendElement(
|
|
std::move(remote), fallible)) {
|
|
mozalloc_handle_oom(0);
|
|
}
|
|
});
|
|
|
|
// Then, fill in local side (with cross-link to remote only if
|
|
// present)
|
|
RTCOutboundRtpStreamStats local;
|
|
constructCommonOutboundRtpStats(local);
|
|
local.mPacketsSent.Construct(audioStats->packets_sent);
|
|
local.mBytesSent.Construct(audioStats->payload_bytes_sent);
|
|
local.mNackCount.Construct(
|
|
audioStats->rtcp_packet_type_counts.nack_packets);
|
|
local.mHeaderBytesSent.Construct(
|
|
audioStats->header_and_padding_bytes_sent);
|
|
local.mRetransmittedPacketsSent.Construct(
|
|
audioStats->retransmitted_packets_sent);
|
|
local.mRetransmittedBytesSent.Construct(
|
|
audioStats->retransmitted_bytes_sent);
|
|
/*
|
|
* Potential new stats that are now available upstream.
|
|
* Note: when we last tried exposing this we were getting
|
|
* targetBitrate for audio was ending up as 0. We did not
|
|
* investigate why.
|
|
local.mTargetBitrate.Construct(audioStats->target_bitrate_bps);
|
|
*/
|
|
if (!report->mOutboundRtpStreamStats.AppendElement(std::move(local),
|
|
fallible)) {
|
|
mozalloc_handle_oom(0);
|
|
}
|
|
|
|
// TODO(bug 1804678): Use RTCAudioSourceStats
|
|
RTCMediaSourceStats mediaSourceStats;
|
|
constructCommonMediaSourceStats(mediaSourceStats);
|
|
if (!report->mMediaSourceStats.AppendElement(
|
|
std::move(mediaSourceStats), fallible)) {
|
|
mozalloc_handle_oom(0);
|
|
}
|
|
});
|
|
|
|
asVideo.apply([&](auto& aConduit) {
|
|
Maybe<webrtc::VideoSendStream::Stats> videoStats =
|
|
aConduit->GetSenderStats();
|
|
if (videoStats.isNothing()) {
|
|
return;
|
|
}
|
|
|
|
Maybe<webrtc::VideoSendStream::StreamStats> streamStats;
|
|
auto kv = videoStats->substreams.find(ssrc);
|
|
if (kv != videoStats->substreams.end()) {
|
|
streamStats = Some(kv->second);
|
|
}
|
|
|
|
if (!streamStats) {
|
|
// By spec: "The lifetime of all RTP monitored objects starts
|
|
// when the RTP stream is first used: When the first RTP packet
|
|
// is sent or received on the SSRC it represents"
|
|
return;
|
|
}
|
|
|
|
aConduit->GetAssociatedLocalRtxSSRC(ssrc).apply(
|
|
[&](const auto rtxSsrc) {
|
|
auto kv = videoStats->substreams.find(rtxSsrc);
|
|
if (kv != videoStats->substreams.end()) {
|
|
streamStats->rtp_stats.Add(kv->second.rtp_stats);
|
|
}
|
|
});
|
|
|
|
if (streamStats->rtp_stats.first_packet_time ==
|
|
webrtc::Timestamp::PlusInfinity()) {
|
|
return;
|
|
}
|
|
|
|
// First, fill in remote stat with rtcp receiver data, if present.
|
|
// ReceiverReports have less information than SenderReports, so fill
|
|
// in what we can.
|
|
if (streamStats->report_block_data) {
|
|
const webrtc::ReportBlockData& rtcpReportData =
|
|
*streamStats->report_block_data;
|
|
RTCRemoteInboundRtpStreamStats remote;
|
|
remote.mJitter.Construct(
|
|
static_cast<double>(rtcpReportData.jitter()) /
|
|
webrtc::kVideoPayloadTypeFrequency);
|
|
remote.mPacketsLost.Construct(rtcpReportData.cumulative_lost());
|
|
if (rtcpReportData.has_rtt()) {
|
|
remote.mRoundTripTime.Construct(
|
|
static_cast<double>(rtcpReportData.last_rtt().ms()) /
|
|
1000.0);
|
|
}
|
|
constructCommonRemoteInboundRtpStats(remote, rtcpReportData);
|
|
remote.mTotalRoundTripTime.Construct(
|
|
streamStats->report_block_data->sum_rtts().ms() / 1000.0);
|
|
remote.mFractionLost.Construct(
|
|
static_cast<float>(rtcpReportData.fraction_lost_raw()) /
|
|
(1 << 8));
|
|
remote.mRoundTripTimeMeasurements.Construct(
|
|
streamStats->report_block_data->num_rtts());
|
|
if (!report->mRemoteInboundRtpStreamStats.AppendElement(
|
|
std::move(remote), fallible)) {
|
|
mozalloc_handle_oom(0);
|
|
}
|
|
}
|
|
|
|
// Then, fill in local side (with cross-link to remote only if
|
|
// present)
|
|
RTCOutboundRtpStreamStats local;
|
|
constructCommonOutboundRtpStats(local);
|
|
if (auto it = videoSsrcToRidMap.find(ssrc);
|
|
it != videoSsrcToRidMap.end() && it->second != "") {
|
|
local.mRid.Construct(NS_ConvertUTF8toUTF16(it->second).get());
|
|
}
|
|
local.mPacketsSent.Construct(
|
|
streamStats->rtp_stats.transmitted.packets);
|
|
local.mBytesSent.Construct(
|
|
streamStats->rtp_stats.transmitted.payload_bytes);
|
|
local.mNackCount.Construct(
|
|
streamStats->rtcp_packet_type_counts.nack_packets);
|
|
local.mFirCount.Construct(
|
|
streamStats->rtcp_packet_type_counts.fir_packets);
|
|
local.mPliCount.Construct(
|
|
streamStats->rtcp_packet_type_counts.pli_packets);
|
|
local.mFramesEncoded.Construct(streamStats->frames_encoded);
|
|
if (streamStats->qp_sum) {
|
|
local.mQpSum.Construct(*streamStats->qp_sum);
|
|
}
|
|
local.mHeaderBytesSent.Construct(
|
|
streamStats->rtp_stats.transmitted.header_bytes +
|
|
streamStats->rtp_stats.transmitted.padding_bytes);
|
|
local.mRetransmittedPacketsSent.Construct(
|
|
streamStats->rtp_stats.retransmitted.packets);
|
|
local.mRetransmittedBytesSent.Construct(
|
|
streamStats->rtp_stats.retransmitted.payload_bytes);
|
|
local.mTotalEncodedBytesTarget.Construct(
|
|
videoStats->total_encoded_bytes_target);
|
|
local.mFrameWidth.Construct(streamStats->width);
|
|
local.mFrameHeight.Construct(streamStats->height);
|
|
local.mFramesPerSecond.Construct(streamStats->encode_frame_rate);
|
|
local.mFramesSent.Construct(streamStats->frames_encoded);
|
|
local.mHugeFramesSent.Construct(streamStats->huge_frames_sent);
|
|
local.mTotalEncodeTime.Construct(
|
|
double(streamStats->total_encode_time_ms) / 1000.);
|
|
/*
|
|
* Potential new stats that are now available upstream.
|
|
local.mTargetBitrate.Construct(videoStats->target_media_bitrate_bps);
|
|
*/
|
|
if (!report->mOutboundRtpStreamStats.AppendElement(std::move(local),
|
|
fallible)) {
|
|
mozalloc_handle_oom(0);
|
|
}
|
|
|
|
RTCVideoSourceStats videoSourceStats;
|
|
constructCommonMediaSourceStats(videoSourceStats);
|
|
// webrtc::VideoSendStream::Stats does not have width/height. We
|
|
// might be able to get this somewhere else?
|
|
// videoStats->frames is not documented, but looking at the
|
|
// implementation it appears to be the number of frames inputted to
|
|
// the encoder, which ought to work.
|
|
videoSourceStats.mFrames.Construct(videoStats->frames);
|
|
videoSourceStats.mFramesPerSecond.Construct(
|
|
videoStats->input_frame_rate);
|
|
auto resolution = aConduit->GetLastResolution();
|
|
resolution.apply([&](const auto& aResolution) {
|
|
videoSourceStats.mWidth.Construct(aResolution.width);
|
|
videoSourceStats.mHeight.Construct(aResolution.height);
|
|
});
|
|
if (!report->mVideoSourceStats.AppendElement(
|
|
std::move(videoSourceStats), fallible)) {
|
|
mozalloc_handle_oom(0);
|
|
}
|
|
});
|
|
}
|
|
|
|
return RTCStatsPromise::CreateAndResolve(std::move(report), __func__);
|
|
}));
|
|
|
|
if (!aSkipIceStats && GetJsepTransceiver().mTransport.mComponents) {
|
|
promises.AppendElement(mTransportHandler->GetIceStats(
|
|
GetJsepTransceiver().mTransport.mTransportId,
|
|
mPipeline->GetTimestampMaker().GetNow().ToDom()));
|
|
}
|
|
|
|
return promises;
|
|
}
|
|
|
|
void RTCRtpSender::GetCapabilities(const GlobalObject&, const nsAString& aKind,
|
|
Nullable<dom::RTCRtpCapabilities>& aResult) {
|
|
PeerConnectionImpl::GetCapabilities(aKind, aResult, sdp::Direction::kSend);
|
|
}
|
|
|
|
void RTCRtpSender::WarnAboutBadSetParameters(const nsCString& aError) {
|
|
nsCString warning(
|
|
"WARNING! Invalid setParameters call detected! The good news? Firefox "
|
|
"supports sendEncodings in addTransceiver now, so we ask that you switch "
|
|
"over to using the parameters code you use for other browsers. Thank you "
|
|
"for your patience and support. The specific error was: ");
|
|
warning += aError;
|
|
mPc->SendWarningToConsole(warning);
|
|
}
|
|
|
|
nsCString RTCRtpSender::GetEffectiveTLDPlus1() const {
|
|
return mPc->GetEffectiveTLDPlus1();
|
|
}
|
|
|
|
already_AddRefed<Promise> RTCRtpSender::SetParameters(
|
|
const dom::RTCRtpSendParameters& aParameters, ErrorResult& aError) {
|
|
dom::RTCRtpSendParameters paramsCopy(aParameters);
|
|
// When the setParameters method is called, the user agent MUST run the
|
|
// following steps:
|
|
// Let parameters be the method's first argument.
|
|
// Let sender be the RTCRtpSender object on which setParameters is invoked.
|
|
// Let transceiver be the RTCRtpTransceiver object associated with sender
|
|
// (i.e.sender is transceiver.[[Sender]]).
|
|
|
|
RefPtr<dom::Promise> p = MakePromise(aError);
|
|
if (aError.Failed()) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (mPc->IsClosed()) {
|
|
p->MaybeRejectWithInvalidStateError("Peer connection is closed");
|
|
return p.forget();
|
|
}
|
|
|
|
// If transceiver.[[Stopping]] is true, return a promise rejected with a newly
|
|
// created InvalidStateError.
|
|
if (mTransceiver->Stopping()) {
|
|
p->MaybeRejectWithInvalidStateError(
|
|
"This sender's transceiver is stopping/stopped");
|
|
return p.forget();
|
|
}
|
|
|
|
// If sender.[[LastReturnedParameters]] is null, return a promise rejected
|
|
// with a newly created InvalidStateError.
|
|
if (!mLastReturnedParameters.isSome()) {
|
|
nsCString error;
|
|
if (mLastTransactionId.isSome() && paramsCopy.mTransactionId.WasPassed() &&
|
|
*mLastTransactionId == paramsCopy.mTransactionId.Value()) {
|
|
error =
|
|
"Event loop was relinquished between getParameters and setParameters "
|
|
"calls";
|
|
} else {
|
|
error = "Cannot call setParameters without first calling getParameters";
|
|
}
|
|
|
|
if (mAllowOldSetParameters) {
|
|
if (!mHaveWarnedBecauseNoGetParameters) {
|
|
mHaveWarnedBecauseNoGetParameters = true;
|
|
mozilla::glean::rtcrtpsender_setparameters::warn_no_getparameters
|
|
.AddToNumerator(1);
|
|
}
|
|
WarnAboutBadSetParameters(error);
|
|
} else {
|
|
if (!mHaveFailedBecauseNoGetParameters) {
|
|
mHaveFailedBecauseNoGetParameters = true;
|
|
mozilla::glean::rtcrtpsender_setparameters::fail_no_getparameters
|
|
.AddToNumerator(1);
|
|
}
|
|
p->MaybeRejectWithInvalidStateError(error);
|
|
return p.forget();
|
|
}
|
|
}
|
|
|
|
// According to the spec, our consistency checking is based on
|
|
// [[LastReturnedParameters]], but if we're letting
|
|
// [[LastReturnedParameters]]==null slide, we still want to do
|
|
// consistency checking on _something_ so we can warn implementers if they
|
|
// are messing that up also. Just find something, _anything_, to do that
|
|
// checking with.
|
|
// TODO(bug 1803388): Remove this stuff once it is no longer needed.
|
|
// TODO(bug 1803389): Remove the glean errors once they are no longer needed.
|
|
Maybe<RTCRtpSendParameters> oldParams;
|
|
if (mAllowOldSetParameters) {
|
|
if (mLastReturnedParameters.isSome()) {
|
|
oldParams = mLastReturnedParameters;
|
|
} else if (mPendingParameters.isSome()) {
|
|
oldParams = mPendingParameters;
|
|
} else {
|
|
oldParams = Some(mParameters);
|
|
}
|
|
MOZ_ASSERT(oldParams.isSome());
|
|
} else {
|
|
oldParams = mLastReturnedParameters;
|
|
}
|
|
MOZ_ASSERT(oldParams.isSome());
|
|
|
|
// Validate parameters by running the following steps:
|
|
// Let encodings be parameters.encodings.
|
|
// Let codecs be parameters.codecs.
|
|
// Let N be the number of RTCRtpEncodingParameters stored in
|
|
// sender.[[SendEncodings]].
|
|
// If any of the following conditions are met,
|
|
// return a promise rejected with a newly created InvalidModificationError:
|
|
|
|
bool pendingRidChangeFromCompatMode = false;
|
|
// encodings.length is different from N.
|
|
if (paramsCopy.mEncodings.Length() != oldParams->mEncodings.Length()) {
|
|
nsCString error("Cannot change the number of encodings with setParameters");
|
|
if (!mAllowOldSetParameters) {
|
|
if (!mHaveFailedBecauseEncodingCountChange) {
|
|
mHaveFailedBecauseEncodingCountChange = true;
|
|
mozilla::glean::rtcrtpsender_setparameters::fail_length_changed
|
|
.AddToNumerator(1);
|
|
}
|
|
p->MaybeRejectWithInvalidModificationError(error);
|
|
return p.forget();
|
|
}
|
|
// Make sure we don't use the old rids in SyncToJsep while we wait for the
|
|
// queued task below to update mParameters.
|
|
pendingRidChangeFromCompatMode = true;
|
|
mSimulcastEnvelopeSet = true;
|
|
if (!mHaveWarnedBecauseEncodingCountChange) {
|
|
mHaveWarnedBecauseEncodingCountChange = true;
|
|
mozilla::glean::rtcrtpsender_setparameters::warn_length_changed
|
|
.AddToNumerator(1);
|
|
}
|
|
WarnAboutBadSetParameters(error);
|
|
} else {
|
|
// encodings has been re-ordered.
|
|
for (size_t i = 0; i < paramsCopy.mEncodings.Length(); ++i) {
|
|
const auto& oldEncoding = oldParams->mEncodings[i];
|
|
const auto& newEncoding = paramsCopy.mEncodings[i];
|
|
if (oldEncoding.mRid != newEncoding.mRid) {
|
|
nsCString error("Cannot change rid, or reorder encodings");
|
|
if (!mHaveFailedBecauseRidChange) {
|
|
mHaveFailedBecauseRidChange = true;
|
|
mozilla::glean::rtcrtpsender_setparameters::fail_rid_changed
|
|
.AddToNumerator(1);
|
|
}
|
|
p->MaybeRejectWithInvalidModificationError(error);
|
|
return p.forget();
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO(bug 1803388): Handle this in webidl, once we stop allowing the old
|
|
// setParameters style.
|
|
if (!paramsCopy.mTransactionId.WasPassed()) {
|
|
nsCString error("transactionId is not set!");
|
|
if (!mAllowOldSetParameters) {
|
|
if (!mHaveFailedBecauseNoTransactionId) {
|
|
mHaveFailedBecauseNoTransactionId = true;
|
|
mozilla::glean::rtcrtpsender_setparameters::fail_no_transactionid
|
|
.AddToNumerator(1);
|
|
}
|
|
p->MaybeRejectWithTypeError(error);
|
|
return p.forget();
|
|
}
|
|
if (!mHaveWarnedBecauseNoTransactionId) {
|
|
mHaveWarnedBecauseNoTransactionId = true;
|
|
mozilla::glean::rtcrtpsender_setparameters::warn_no_transactionid
|
|
.AddToNumerator(1);
|
|
}
|
|
WarnAboutBadSetParameters(error);
|
|
} else if (oldParams->mTransactionId.WasPassed() &&
|
|
oldParams->mTransactionId != paramsCopy.mTransactionId) {
|
|
// Any parameter in parameters is marked as a Read-only parameter (such as
|
|
// RID) and has a value that is different from the corresponding parameter
|
|
// value in sender.[[LastReturnedParameters]]. Note that this also applies
|
|
// to transactionId.
|
|
// Don't throw this error if letting the "stale getParameters" case slide.
|
|
nsCString error(
|
|
"Cannot change transaction id: call getParameters, modify the result, "
|
|
"and then call setParameters");
|
|
if (!mHaveFailedBecauseStaleTransactionId) {
|
|
mHaveFailedBecauseStaleTransactionId = true;
|
|
mozilla::glean::rtcrtpsender_setparameters::fail_stale_transactionid
|
|
.AddToNumerator(1);
|
|
}
|
|
p->MaybeRejectWithInvalidModificationError(error);
|
|
return p.forget();
|
|
}
|
|
|
|
// This could conceivably happen if we are allowing the old setParameters
|
|
// behavior.
|
|
if (!paramsCopy.mEncodings.Length()) {
|
|
nsCString error("Cannot set an empty encodings array");
|
|
if (!mAllowOldSetParameters) {
|
|
if (!mHaveFailedBecauseNoEncodings) {
|
|
mHaveFailedBecauseNoEncodings = true;
|
|
mozilla::glean::rtcrtpsender_setparameters::fail_no_encodings
|
|
.AddToNumerator(1);
|
|
}
|
|
|
|
p->MaybeRejectWithInvalidModificationError(error);
|
|
return p.forget();
|
|
}
|
|
// TODO: Add some warning telemetry here
|
|
WarnAboutBadSetParameters(error);
|
|
// Just don't do this; it's stupid.
|
|
paramsCopy.mEncodings = oldParams->mEncodings;
|
|
}
|
|
|
|
if (!(oldParams->mCodecs == paramsCopy.mCodecs)) {
|
|
nsCString error("RTCRtpParameters.codecs is a read-only parameter");
|
|
if (!mAllowOldSetParameters) {
|
|
p->MaybeRejectWithInvalidModificationError(error);
|
|
return p.forget();
|
|
}
|
|
WarnAboutBadSetParameters(error);
|
|
}
|
|
|
|
// TODO: Verify remaining read-only parameters
|
|
// headerExtensions (bug 1765851)
|
|
// rtcp (bug 1765852)
|
|
|
|
// CheckAndRectifyEncodings handles the following steps:
|
|
// If transceiver kind is "audio", remove the scaleResolutionDownBy member
|
|
// from all encodings that contain one.
|
|
//
|
|
// If transceiver kind is "video", and any encoding in encodings contains a
|
|
// scaleResolutionDownBy member whose value is less than 1.0, return a
|
|
// promise rejected with a newly created RangeError.
|
|
//
|
|
// Verify that each encoding in encodings has a maxFramerate member whose
|
|
// value is greater than or equal to 0.0. If one of the maxFramerate values
|
|
// does not meet this requirement, return a promise rejected with a newly
|
|
// created RangeError.
|
|
ErrorResult rv;
|
|
CheckAndRectifyEncodings(paramsCopy.mEncodings, mTransceiver->IsVideo(), rv);
|
|
if (rv.Failed()) {
|
|
if (!mHaveFailedBecauseOtherError) {
|
|
mHaveFailedBecauseOtherError = true;
|
|
mozilla::glean::rtcrtpsender_setparameters::fail_other.AddToNumerator(1);
|
|
}
|
|
p->MaybeReject(std::move(rv));
|
|
return p.forget();
|
|
}
|
|
|
|
// If transceiver kind is "video", then for each encoding in encodings that
|
|
// doesn't contain a scaleResolutionDownBy member, add a
|
|
// scaleResolutionDownBy member with the value 1.0.
|
|
if (mTransceiver->IsVideo()) {
|
|
for (auto& encoding : paramsCopy.mEncodings) {
|
|
if (!encoding.mScaleResolutionDownBy.WasPassed()) {
|
|
encoding.mScaleResolutionDownBy.Construct(1.0);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Let p be a new promise. (see above)
|
|
|
|
// In parallel, configure the media stack to use parameters to transmit
|
|
// sender.[[SenderTrack]].
|
|
// Right now this is infallible. That may change someday.
|
|
|
|
// We need to put this in a member variable, since MaybeUpdateConduit needs it
|
|
// This also allows PeerConnectionImpl to detect when there is a pending
|
|
// setParameters, which has implcations for the handling of
|
|
// setRemoteDescription.
|
|
mPendingRidChangeFromCompatMode = pendingRidChangeFromCompatMode;
|
|
mPendingParameters = Some(paramsCopy);
|
|
uint32_t serialNumber = ++mNumSetParametersCalls;
|
|
MaybeUpdateConduit();
|
|
|
|
// If we have a degradation value passed convert it from a
|
|
// dom::RTCDegradationPreference to a webrtc::DegradationPreference and set
|
|
// mVideoDegradationPreference which will trigger the onconfigure change in
|
|
// the videoConduit.
|
|
if (paramsCopy.mDegradationPreference.WasPassed()) {
|
|
const auto degradationPreference = [&] {
|
|
switch (paramsCopy.mDegradationPreference.Value()) {
|
|
case mozilla::dom::RTCDegradationPreference::Balanced:
|
|
return webrtc::DegradationPreference::BALANCED;
|
|
case mozilla::dom::RTCDegradationPreference::Maintain_framerate:
|
|
return webrtc::DegradationPreference::MAINTAIN_FRAMERATE;
|
|
case mozilla::dom::RTCDegradationPreference::Maintain_resolution:
|
|
return webrtc::DegradationPreference::MAINTAIN_RESOLUTION;
|
|
}
|
|
MOZ_CRASH("Unexpected RTCDegradationPreference");
|
|
};
|
|
mVideoDegradationPreference = degradationPreference();
|
|
} else {
|
|
// Default to disabled when unset to allow for correct degradation
|
|
mVideoDegradationPreference = webrtc::DegradationPreference::DISABLED;
|
|
}
|
|
|
|
// If the media stack is successfully configured with parameters,
|
|
// queue a task to run the following steps:
|
|
GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
|
|
__func__,
|
|
[this, self = RefPtr<RTCRtpSender>(this), p, paramsCopy, serialNumber] {
|
|
// Set sender.[[LastReturnedParameters]] to null.
|
|
mLastReturnedParameters = Nothing();
|
|
// Set sender.[[SendEncodings]] to parameters.encodings.
|
|
mParameters.mEncodings = paramsCopy.mEncodings;
|
|
UpdateRestorableEncodings(mParameters.mEncodings);
|
|
// If we had a valid degradation preference passed in store it in
|
|
// mParameters so we can return it if needed via GetParameters calls.
|
|
mParameters.mDegradationPreference.Reset();
|
|
if (paramsCopy.mDegradationPreference.WasPassed()) {
|
|
mParameters.mDegradationPreference.Construct(
|
|
paramsCopy.mDegradationPreference.Value());
|
|
}
|
|
// Only clear mPendingParameters if it matches; there could have been
|
|
// back-to-back calls to setParameters, and we only want to clear this
|
|
// if no subsequent setParameters is pending.
|
|
if (serialNumber == mNumSetParametersCalls) {
|
|
mPendingParameters = Nothing();
|
|
// Ok, nothing has called SyncToJsep while this async task was
|
|
// pending. No need for special handling anymore.
|
|
mPendingRidChangeFromCompatMode = false;
|
|
}
|
|
MOZ_ASSERT(mParameters.mEncodings.Length());
|
|
// Resolve p with undefined.
|
|
p->MaybeResolveWithUndefined();
|
|
}));
|
|
|
|
// Return p.
|
|
return p.forget();
|
|
}
|
|
|
|
// static
|
|
void RTCRtpSender::CheckAndRectifyEncodings(
|
|
Sequence<RTCRtpEncodingParameters>& aEncodings, bool aVideo,
|
|
ErrorResult& aRv) {
|
|
// 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.
|
|
for (const auto& encoding : aEncodings) {
|
|
if (encoding.mRid.WasPassed()) {
|
|
std::string utf8Rid = NS_ConvertUTF16toUTF8(encoding.mRid.Value()).get();
|
|
std::string error;
|
|
if (!SdpRidAttributeList::CheckRidValidity(utf8Rid, &error)) {
|
|
aRv.ThrowTypeError(nsCString(error));
|
|
return;
|
|
}
|
|
if (utf8Rid.size() > SdpRidAttributeList::kMaxRidLength) {
|
|
std::ostringstream ss;
|
|
ss << "Rid can be at most " << SdpRidAttributeList::kMaxRidLength
|
|
<< " characters long (due to internal limitations)";
|
|
aRv.ThrowTypeError(nsCString(ss.str()));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (aEncodings.Length() > 1) {
|
|
// If some but not all encodings contain a rid member, throw a TypeError.
|
|
// rid must be set if there is more than one encoding
|
|
// NOTE: Since rid is read-only, and the number of encodings cannot grow,
|
|
// this should never happen in setParameters.
|
|
for (const auto& encoding : aEncodings) {
|
|
if (!encoding.mRid.WasPassed()) {
|
|
aRv.ThrowTypeError("Missing rid");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
// NOTE: Since rid is read-only, and the number of encodings cannot grow,
|
|
// this should never happen in setParameters.
|
|
std::set<nsString> uniqueRids;
|
|
for (const auto& encoding : aEncodings) {
|
|
if (uniqueRids.count(encoding.mRid.Value())) {
|
|
aRv.ThrowTypeError("Duplicate rid");
|
|
return;
|
|
}
|
|
uniqueRids.insert(encoding.mRid.Value());
|
|
}
|
|
}
|
|
// TODO: ptime/adaptivePtime validation (bug 1733647)
|
|
|
|
// If kind is "audio", remove the scaleResolutionDownBy member from all
|
|
// encodings that contain one.
|
|
if (!aVideo) {
|
|
for (auto& encoding : aEncodings) {
|
|
if (encoding.mScaleResolutionDownBy.WasPassed()) {
|
|
encoding.mScaleResolutionDownBy.Reset();
|
|
}
|
|
if (encoding.mMaxFramerate.WasPassed()) {
|
|
encoding.mMaxFramerate.Reset();
|
|
}
|
|
}
|
|
}
|
|
|
|
// If any encoding contains a scaleResolutionDownBy member whose value is
|
|
// less than 1.0, throw a RangeError.
|
|
for (const auto& encoding : aEncodings) {
|
|
if (encoding.mScaleResolutionDownBy.WasPassed()) {
|
|
if (encoding.mScaleResolutionDownBy.Value() < 1.0f) {
|
|
aRv.ThrowRangeError("scaleResolutionDownBy must be >= 1.0");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
for (const auto& encoding : aEncodings) {
|
|
if (encoding.mMaxFramerate.WasPassed()) {
|
|
if (encoding.mMaxFramerate.Value() < 0.0f) {
|
|
aRv.ThrowRangeError("maxFramerate must be non-negative");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void RTCRtpSender::GetParameters(RTCRtpSendParameters& aParameters) {
|
|
MOZ_ASSERT(mParameters.mEncodings.Length());
|
|
// If sender.[[LastReturnedParameters]] is not null, return
|
|
// sender.[[LastReturnedParameters]], and abort these steps.
|
|
if (mLastReturnedParameters.isSome()) {
|
|
aParameters = *mLastReturnedParameters;
|
|
return;
|
|
}
|
|
|
|
// Let result be a new RTCRtpSendParameters dictionary constructed as follows:
|
|
|
|
// transactionId is set to a new unique identifier
|
|
aParameters.mTransactionId.Construct(mPc->GenerateUUID());
|
|
|
|
// encodings is set to the value of the [[SendEncodings]] internal slot.
|
|
aParameters.mEncodings = mParameters.mEncodings;
|
|
|
|
// The headerExtensions sequence is populated based on the header extensions
|
|
// that have been negotiated for sending
|
|
// TODO(bug 1765851): We do not support this yet
|
|
// aParameters.mHeaderExtensions.Construct();
|
|
|
|
// rtcp.cname is set to the CNAME of the associated RTCPeerConnection.
|
|
// rtcp.reducedSize is set to true if reduced-size RTCP has been negotiated
|
|
// for sending, and false otherwise.
|
|
// TODO(bug 1765852): We do not support this yet
|
|
aParameters.mRtcp.Construct();
|
|
aParameters.mRtcp.Value().mCname.Construct();
|
|
aParameters.mRtcp.Value().mReducedSize.Construct(false);
|
|
if (mParameters.mDegradationPreference.WasPassed()) {
|
|
aParameters.mDegradationPreference.Construct(
|
|
mParameters.mDegradationPreference.Value());
|
|
}
|
|
aParameters.mHeaderExtensions.Construct();
|
|
if (mParameters.mCodecs.WasPassed()) {
|
|
aParameters.mCodecs.Construct(mParameters.mCodecs.Value());
|
|
}
|
|
|
|
// Set sender.[[LastReturnedParameters]] to result.
|
|
mLastReturnedParameters = Some(aParameters);
|
|
|
|
// Used to help with warning strings
|
|
mLastTransactionId = Some(aParameters.mTransactionId.Value());
|
|
|
|
// Queue a task that sets sender.[[LastReturnedParameters]] to null.
|
|
GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
|
|
__func__, [this, self = RefPtr<RTCRtpSender>(this)] {
|
|
mLastReturnedParameters = Nothing();
|
|
}));
|
|
}
|
|
|
|
bool operator==(const RTCRtpEncodingParameters& a1,
|
|
const RTCRtpEncodingParameters& a2) {
|
|
// webidl does not generate types that are equality comparable
|
|
return a1.mActive == a2.mActive && a1.mMaxBitrate == a2.mMaxBitrate &&
|
|
a1.mMaxFramerate == a2.mMaxFramerate && a1.mPriority == a2.mPriority &&
|
|
a1.mRid == a2.mRid &&
|
|
a1.mScaleResolutionDownBy == a2.mScaleResolutionDownBy;
|
|
}
|
|
|
|
// static
|
|
void RTCRtpSender::ApplyJsEncodingToConduitEncoding(
|
|
const RTCRtpEncodingParameters& aJsEncoding,
|
|
VideoCodecConfig::Encoding* aConduitEncoding) {
|
|
aConduitEncoding->active = aJsEncoding.mActive;
|
|
if (aJsEncoding.mMaxBitrate.WasPassed()) {
|
|
aConduitEncoding->constraints.maxBr = aJsEncoding.mMaxBitrate.Value();
|
|
}
|
|
if (aJsEncoding.mMaxFramerate.WasPassed()) {
|
|
aConduitEncoding->constraints.maxFps =
|
|
Some(aJsEncoding.mMaxFramerate.Value());
|
|
}
|
|
if (aJsEncoding.mScaleResolutionDownBy.WasPassed()) {
|
|
// Optional does not have a valueOr, despite being based on Maybe
|
|
// :(
|
|
aConduitEncoding->constraints.scaleDownBy =
|
|
aJsEncoding.mScaleResolutionDownBy.Value();
|
|
} else {
|
|
aConduitEncoding->constraints.scaleDownBy = 1.0f;
|
|
}
|
|
}
|
|
|
|
void RTCRtpSender::UpdateRestorableEncodings(
|
|
const Sequence<RTCRtpEncodingParameters>& aEncodings) {
|
|
MOZ_ASSERT(aEncodings.Length());
|
|
|
|
if (GetJsepTransceiver().mSendTrack.GetNegotiatedDetails()) {
|
|
// Once initial negotiation completes, we are no longer allowed to restore
|
|
// the unicast encoding.
|
|
mUnicastEncoding.reset();
|
|
} else if (mParameters.mEncodings.Length() == 1 &&
|
|
!mParameters.mEncodings[0].mRid.WasPassed()) {
|
|
// If we have not completed the initial negotiation, and we currently are
|
|
// ridless unicast, we need to save our unicast encoding in case a
|
|
// rollback occurs.
|
|
mUnicastEncoding = Some(mParameters.mEncodings[0]);
|
|
}
|
|
}
|
|
|
|
Sequence<RTCRtpEncodingParameters> RTCRtpSender::ToSendEncodings(
|
|
const std::vector<std::string>& aRids) const {
|
|
MOZ_ASSERT(!aRids.empty());
|
|
|
|
Sequence<RTCRtpEncodingParameters> result;
|
|
// If sendEncodings is given as input to this algorithm, and is non-empty,
|
|
// set the [[SendEncodings]] slot to sendEncodings.
|
|
for (const auto& rid : aRids) {
|
|
MOZ_ASSERT(!rid.empty());
|
|
RTCRtpEncodingParameters encoding;
|
|
encoding.mActive = true;
|
|
encoding.mRid.Construct(NS_ConvertUTF8toUTF16(rid.c_str()));
|
|
Unused << result.AppendElement(encoding, fallible);
|
|
}
|
|
|
|
// If sendEncodings is non-empty, set each encoding's scaleResolutionDownBy
|
|
// to 2^(length of sendEncodings - encoding index - 1).
|
|
if (mTransceiver->IsVideo()) {
|
|
double scale = 1.0f;
|
|
for (auto it = result.rbegin(); it != result.rend(); ++it) {
|
|
it->mScaleResolutionDownBy.Construct(scale);
|
|
scale *= 2;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void RTCRtpSender::MaybeGetJsepRids() {
|
|
MOZ_ASSERT(!mSimulcastEnvelopeSet);
|
|
MOZ_ASSERT(mParameters.mEncodings.Length());
|
|
|
|
auto jsepRids = GetJsepTransceiver().mSendTrack.GetRids();
|
|
if (!jsepRids.empty()) {
|
|
UpdateRestorableEncodings(mParameters.mEncodings);
|
|
if (jsepRids.size() != 1 || !jsepRids[0].empty()) {
|
|
// JSEP is using at least one rid. Stomp our single ridless encoding
|
|
mParameters.mEncodings = ToSendEncodings(jsepRids);
|
|
}
|
|
mSimulcastEnvelopeSet = true;
|
|
mSimulcastEnvelopeSetByJSEP = true;
|
|
}
|
|
}
|
|
|
|
Sequence<RTCRtpEncodingParameters> RTCRtpSender::GetMatchingEncodings(
|
|
const std::vector<std::string>& aRids) const {
|
|
Sequence<RTCRtpEncodingParameters> result;
|
|
|
|
if (!aRids.empty() && !aRids[0].empty()) {
|
|
// Simulcast, or unicast with rid
|
|
for (const auto& encoding : mParameters.mEncodings) {
|
|
for (const auto& rid : aRids) {
|
|
auto utf16Rid = NS_ConvertUTF8toUTF16(rid.c_str());
|
|
if (!encoding.mRid.WasPassed() || (utf16Rid == encoding.mRid.Value())) {
|
|
auto encodingCopy(encoding);
|
|
if (!encodingCopy.mRid.WasPassed()) {
|
|
encodingCopy.mRid.Construct(NS_ConvertUTF8toUTF16(rid.c_str()));
|
|
}
|
|
Unused << result.AppendElement(encodingCopy, fallible);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we're allowing the old setParameters behavior, we _might_ be able to
|
|
// get into this situation even if there were rids above. Be extra careful.
|
|
// Under normal circumstances, this just handles the ridless case.
|
|
if (!result.Length()) {
|
|
// Unicast with no specified rid. Restore mUnicastEncoding, if
|
|
// it exists, otherwise pick the first encoding.
|
|
if (mUnicastEncoding.isSome()) {
|
|
Unused << result.AppendElement(*mUnicastEncoding, fallible);
|
|
} else {
|
|
Unused << result.AppendElement(mParameters.mEncodings[0], fallible);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void RTCRtpSender::SetStreams(
|
|
const Sequence<OwningNonNull<DOMMediaStream>>& aStreams, ErrorResult& aRv) {
|
|
if (mPc->IsClosed()) {
|
|
aRv.ThrowInvalidStateError(
|
|
"Cannot call setStreams if the peer connection is closed");
|
|
return;
|
|
}
|
|
|
|
SetStreamsImpl(aStreams);
|
|
mPc->UpdateNegotiationNeeded();
|
|
}
|
|
|
|
void RTCRtpSender::SetStreamsImpl(
|
|
const Sequence<OwningNonNull<DOMMediaStream>>& aStreams) {
|
|
mStreams.Clear();
|
|
std::set<nsString> ids;
|
|
for (const auto& stream : aStreams) {
|
|
nsString id;
|
|
stream->GetId(id);
|
|
if (!ids.count(id)) {
|
|
ids.insert(id);
|
|
mStreams.AppendElement(stream);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RTCRtpSender::GetStreams(nsTArray<RefPtr<DOMMediaStream>>& aStreams) {
|
|
aStreams = mStreams.Clone();
|
|
}
|
|
|
|
class ReplaceTrackOperation final : public PeerConnectionImpl::Operation {
|
|
public:
|
|
ReplaceTrackOperation(PeerConnectionImpl* aPc,
|
|
const RefPtr<RTCRtpTransceiver>& aTransceiver,
|
|
const RefPtr<MediaStreamTrack>& aTrack,
|
|
ErrorResult& aError);
|
|
NS_DECL_ISUPPORTS_INHERITED
|
|
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ReplaceTrackOperation,
|
|
PeerConnectionImpl::Operation)
|
|
|
|
private:
|
|
MOZ_CAN_RUN_SCRIPT
|
|
RefPtr<dom::Promise> CallImpl(ErrorResult& aError) override;
|
|
~ReplaceTrackOperation() = default;
|
|
RefPtr<RTCRtpTransceiver> mTransceiver;
|
|
RefPtr<MediaStreamTrack> mNewTrack;
|
|
};
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_INHERITED(ReplaceTrackOperation,
|
|
PeerConnectionImpl::Operation, mTransceiver,
|
|
mNewTrack)
|
|
|
|
NS_IMPL_ADDREF_INHERITED(ReplaceTrackOperation, PeerConnectionImpl::Operation)
|
|
NS_IMPL_RELEASE_INHERITED(ReplaceTrackOperation, PeerConnectionImpl::Operation)
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReplaceTrackOperation)
|
|
NS_INTERFACE_MAP_END_INHERITING(PeerConnectionImpl::Operation)
|
|
|
|
ReplaceTrackOperation::ReplaceTrackOperation(
|
|
PeerConnectionImpl* aPc, const RefPtr<RTCRtpTransceiver>& aTransceiver,
|
|
const RefPtr<MediaStreamTrack>& aTrack, ErrorResult& aError)
|
|
: PeerConnectionImpl::Operation(aPc, aError),
|
|
mTransceiver(aTransceiver),
|
|
mNewTrack(aTrack) {}
|
|
|
|
RefPtr<dom::Promise> ReplaceTrackOperation::CallImpl(ErrorResult& aError) {
|
|
RefPtr<RTCRtpSender> sender = mTransceiver->Sender();
|
|
// If transceiver.[[Stopping]] is true, return a promise rejected with a
|
|
// newly created InvalidStateError.
|
|
if (mTransceiver->Stopped() || mTransceiver->Stopping()) {
|
|
RefPtr<dom::Promise> error = sender->MakePromise(aError);
|
|
if (aError.Failed()) {
|
|
return nullptr;
|
|
}
|
|
MOZ_LOG(gSenderLog, LogLevel::Debug,
|
|
("%s Cannot call replaceTrack when transceiver is stopping",
|
|
__FUNCTION__));
|
|
error->MaybeRejectWithInvalidStateError(
|
|
"Cannot call replaceTrack when transceiver is stopping");
|
|
return error;
|
|
}
|
|
|
|
// Let p be a new promise.
|
|
RefPtr<dom::Promise> p = sender->MakePromise(aError);
|
|
if (aError.Failed()) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (!sender->SeamlessTrackSwitch(mNewTrack)) {
|
|
MOZ_LOG(gSenderLog, LogLevel::Info,
|
|
("%s Could not seamlessly replace track", __FUNCTION__));
|
|
p->MaybeRejectWithInvalidModificationError(
|
|
"Could not seamlessly replace track");
|
|
return p;
|
|
}
|
|
|
|
// Queue a task that runs the following steps:
|
|
GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
|
|
__func__, [p, sender, track = mNewTrack]() MOZ_CAN_RUN_SCRIPT_BOUNDARY {
|
|
// If connection.[[IsClosed]] is true, abort these steps.
|
|
// Set sender.[[SenderTrack]] to withTrack.
|
|
if (sender->SetSenderTrackWithClosedCheck(track)) {
|
|
// Resolve p with undefined.
|
|
p->MaybeResolveWithUndefined();
|
|
}
|
|
}));
|
|
|
|
// Return p.
|
|
return p;
|
|
}
|
|
|
|
already_AddRefed<dom::Promise> RTCRtpSender::ReplaceTrack(
|
|
dom::MediaStreamTrack* aWithTrack, ErrorResult& aError) {
|
|
// If withTrack is non-null and withTrack.kind differs from the transceiver
|
|
// kind of transceiver, return a promise rejected with a newly created
|
|
// TypeError.
|
|
if (aWithTrack) {
|
|
nsString newKind;
|
|
aWithTrack->GetKind(newKind);
|
|
nsString oldKind;
|
|
mTransceiver->GetKind(oldKind);
|
|
if (newKind != oldKind) {
|
|
RefPtr<dom::Promise> error = MakePromise(aError);
|
|
if (aError.Failed()) {
|
|
return nullptr;
|
|
}
|
|
error->MaybeRejectWithTypeError(
|
|
"Cannot replaceTrack with a different kind!");
|
|
return error.forget();
|
|
}
|
|
}
|
|
|
|
MOZ_LOG(gSenderLog, LogLevel::Debug,
|
|
("%s[%s]: %s (%p to %p)", mPc->GetHandle().c_str(), GetMid().c_str(),
|
|
__FUNCTION__, mSenderTrack.get(), aWithTrack));
|
|
|
|
// Return the result of chaining the following steps to connection's
|
|
// operations chain:
|
|
RefPtr<PeerConnectionImpl::Operation> op =
|
|
new ReplaceTrackOperation(mPc, mTransceiver, aWithTrack, aError);
|
|
if (aError.Failed()) {
|
|
return nullptr;
|
|
}
|
|
// Static analysis forces us to use a temporary.
|
|
auto pc = mPc;
|
|
return pc->Chain(op, aError);
|
|
}
|
|
|
|
nsPIDOMWindowInner* RTCRtpSender::GetParentObject() const { return mWindow; }
|
|
|
|
already_AddRefed<dom::Promise> RTCRtpSender::MakePromise(
|
|
ErrorResult& aError) const {
|
|
return mPc->MakePromise(aError);
|
|
}
|
|
|
|
bool RTCRtpSender::SeamlessTrackSwitch(
|
|
const RefPtr<MediaStreamTrack>& aWithTrack) {
|
|
// We do not actually update mSenderTrack here! Spec says that happens in a
|
|
// queued task after this is done (this happens in
|
|
// SetSenderTrackWithClosedCheck).
|
|
|
|
mPipeline->SetTrack(aWithTrack);
|
|
|
|
MaybeUpdateConduit();
|
|
|
|
// There may eventually be cases where a renegotiation is necessary to switch.
|
|
return true;
|
|
}
|
|
|
|
void RTCRtpSender::SetTrack(const RefPtr<MediaStreamTrack>& aTrack) {
|
|
// Used for RTCPeerConnection.removeTrack and RTCPeerConnection.addTrack
|
|
if (mTransceiver->Stopping()) {
|
|
return;
|
|
}
|
|
mSenderTrack = aTrack;
|
|
SeamlessTrackSwitch(aTrack);
|
|
if (aTrack) {
|
|
// RFC says (in the section on remote rollback):
|
|
// However, an RtpTransceiver MUST NOT be removed if a track was attached
|
|
// to the RtpTransceiver via the addTrack method.
|
|
mSenderTrackSetByAddTrack = true;
|
|
}
|
|
}
|
|
|
|
bool RTCRtpSender::SetSenderTrackWithClosedCheck(
|
|
const RefPtr<MediaStreamTrack>& aTrack) {
|
|
if (!mPc->IsClosed()) {
|
|
mSenderTrack = aTrack;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void RTCRtpSender::Shutdown() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
mWatchManager.Shutdown();
|
|
mPipeline->Shutdown();
|
|
mPipeline = nullptr;
|
|
if (mTransform) {
|
|
mTransform->GetProxy().SetSender(nullptr);
|
|
}
|
|
}
|
|
|
|
void RTCRtpSender::BreakCycles() {
|
|
mWindow = nullptr;
|
|
mPc = nullptr;
|
|
mSenderTrack = nullptr;
|
|
mTransceiver = nullptr;
|
|
mStreams.Clear();
|
|
mDtmf = nullptr;
|
|
}
|
|
|
|
void RTCRtpSender::Unlink() {
|
|
if (mTransceiver) {
|
|
mTransceiver->Unlink();
|
|
}
|
|
}
|
|
|
|
void RTCRtpSender::UpdateTransport() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
if (!mHaveSetupTransport) {
|
|
mPipeline->SetLevel(GetJsepTransceiver().GetLevel());
|
|
mHaveSetupTransport = true;
|
|
}
|
|
|
|
mPipeline->UpdateTransport_m(GetJsepTransceiver().mTransport.mTransportId,
|
|
nullptr, true);
|
|
}
|
|
|
|
void RTCRtpSender::MaybeUpdateConduit() {
|
|
// NOTE(pkerr) - the Call API requires the both local_ssrc and remote_ssrc be
|
|
// set to a non-zero value or the CreateVideo...Stream call will fail.
|
|
if (NS_WARN_IF(GetJsepTransceiver().mSendTrack.GetSsrcs().empty())) {
|
|
MOZ_ASSERT(
|
|
false,
|
|
"No local ssrcs! This is a bug in the jsep engine, and should never "
|
|
"happen!");
|
|
return;
|
|
}
|
|
|
|
if (!mPipeline) {
|
|
return;
|
|
}
|
|
|
|
bool wasTransmitting = mTransmitting;
|
|
|
|
if (mPipeline->mConduit->type() == MediaSessionConduit::VIDEO) {
|
|
Maybe<VideoConfig> newConfig = GetNewVideoConfig();
|
|
if (newConfig.isSome()) {
|
|
ApplyVideoConfig(*newConfig);
|
|
}
|
|
} else {
|
|
Maybe<AudioConfig> newConfig = GetNewAudioConfig();
|
|
if (newConfig.isSome()) {
|
|
ApplyAudioConfig(*newConfig);
|
|
}
|
|
}
|
|
|
|
if (!mSenderTrack && !wasTransmitting && mTransmitting) {
|
|
MOZ_LOG(gSenderLog, LogLevel::Debug,
|
|
("%s[%s]: %s Starting transmit conduit without send track!",
|
|
mPc->GetHandle().c_str(), GetMid().c_str(), __FUNCTION__));
|
|
}
|
|
}
|
|
|
|
void RTCRtpSender::UpdateParametersCodecs() {
|
|
mParameters.mCodecs.Reset();
|
|
mParameters.mCodecs.Construct();
|
|
|
|
if (GetJsepTransceiver().mSendTrack.GetNegotiatedDetails()) {
|
|
const JsepTrackNegotiatedDetails details(
|
|
*GetJsepTransceiver().mSendTrack.GetNegotiatedDetails());
|
|
if (details.GetEncodingCount()) {
|
|
for (const auto& jsepCodec : details.GetEncoding(0).GetCodecs()) {
|
|
RTCRtpCodecParameters codec;
|
|
RTCRtpTransceiver::ToDomRtpCodecParameters(*jsepCodec, &codec);
|
|
Unused << mParameters.mCodecs.Value().AppendElement(codec, fallible);
|
|
if (jsepCodec->Type() == SdpMediaSection::kVideo) {
|
|
const JsepVideoCodecDescription& videoJsepCodec =
|
|
static_cast<JsepVideoCodecDescription&>(*jsepCodec);
|
|
// In our JSEP implementation, RTX is an addon to an existing codec,
|
|
// not a codec object in its own right. webrtc-pc treats RTX as a
|
|
// separate codec, however.
|
|
if (videoJsepCodec.mRtxEnabled) {
|
|
RTCRtpCodecParameters rtx;
|
|
RTCRtpTransceiver::ToDomRtpCodecParametersRtx(videoJsepCodec, &rtx);
|
|
Unused << mParameters.mCodecs.Value().AppendElement(rtx, fallible);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void RTCRtpSender::SyncFromJsep(const JsepTransceiver& aJsepTransceiver) {
|
|
if (!mSimulcastEnvelopeSet) {
|
|
// JSEP is establishing the simulcast envelope for the first time, right now
|
|
// This is the addTrack (or addTransceiver without sendEncodings) case.
|
|
MaybeGetJsepRids();
|
|
} else if (!aJsepTransceiver.mSendTrack.GetNegotiatedDetails() ||
|
|
!aJsepTransceiver.mSendTrack.IsInHaveRemote()) {
|
|
// Spec says that we do not update our encodings until we're in stable,
|
|
// _unless_ this is the first negotiation.
|
|
std::vector<std::string> rids = aJsepTransceiver.mSendTrack.GetRids();
|
|
if (mSimulcastEnvelopeSetByJSEP && rids.empty()) {
|
|
// JSEP previously set the simulcast envelope, but now it has no opinion
|
|
// regarding unicast/simulcast. This can only happen on rollback of the
|
|
// initial remote offer.
|
|
mParameters.mEncodings = GetMatchingEncodings(rids);
|
|
MOZ_ASSERT(mParameters.mEncodings.Length());
|
|
mSimulcastEnvelopeSetByJSEP = false;
|
|
mSimulcastEnvelopeSet = false;
|
|
} else if (!rids.empty()) {
|
|
// JSEP has an opinion on the simulcast envelope, which trumps anything
|
|
// we have already.
|
|
mParameters.mEncodings = GetMatchingEncodings(rids);
|
|
MOZ_ASSERT(mParameters.mEncodings.Length());
|
|
}
|
|
}
|
|
UpdateParametersCodecs();
|
|
|
|
MaybeUpdateConduit();
|
|
}
|
|
|
|
void RTCRtpSender::SyncToJsep(JsepTransceiver& aJsepTransceiver) const {
|
|
std::vector<std::string> streamIds;
|
|
for (const auto& stream : mStreams) {
|
|
nsString wideStreamId;
|
|
stream->GetId(wideStreamId);
|
|
std::string streamId = NS_ConvertUTF16toUTF8(wideStreamId).get();
|
|
MOZ_ASSERT(!streamId.empty());
|
|
streamIds.push_back(streamId);
|
|
}
|
|
|
|
aJsepTransceiver.mSendTrack.UpdateStreamIds(streamIds);
|
|
|
|
if (mSimulcastEnvelopeSet) {
|
|
std::vector<std::string> rids;
|
|
Maybe<RTCRtpSendParameters> parameters;
|
|
if (mPendingRidChangeFromCompatMode) {
|
|
// *sigh* If we have just let a setParameters change our rids, but we have
|
|
// not yet updated mParameters because the queued task hasn't run yet,
|
|
// we want to set the _new_ rids on the JsepTrack. So, we are forced to
|
|
// grab them from mPendingParameters.
|
|
parameters = mPendingParameters;
|
|
} else {
|
|
parameters = Some(mParameters);
|
|
}
|
|
for (const auto& encoding : parameters->mEncodings) {
|
|
if (encoding.mRid.WasPassed()) {
|
|
rids.push_back(NS_ConvertUTF16toUTF8(encoding.mRid.Value()).get());
|
|
} else {
|
|
rids.push_back("");
|
|
}
|
|
}
|
|
|
|
aJsepTransceiver.mSendTrack.SetRids(rids);
|
|
}
|
|
|
|
if (mTransceiver->IsVideo()) {
|
|
aJsepTransceiver.mSendTrack.SetMaxEncodings(webrtc::kMaxSimulcastStreams);
|
|
} else {
|
|
aJsepTransceiver.mSendTrack.SetMaxEncodings(1);
|
|
}
|
|
|
|
if (mSenderTrackSetByAddTrack) {
|
|
aJsepTransceiver.SetOnlyExistsBecauseOfSetRemote(false);
|
|
}
|
|
}
|
|
|
|
Maybe<RTCRtpSender::VideoConfig> RTCRtpSender::GetNewVideoConfig() {
|
|
// It is possible for SDP to signal that there is a send track, but there not
|
|
// actually be a send track, according to the specification; all that needs to
|
|
// happen is for the transceiver to be configured to send...
|
|
if (!GetJsepTransceiver().mSendTrack.GetNegotiatedDetails()) {
|
|
return Nothing();
|
|
}
|
|
|
|
VideoConfig oldConfig;
|
|
oldConfig.mSsrcs = mSsrcs;
|
|
oldConfig.mLocalRtpExtensions = mLocalRtpExtensions;
|
|
oldConfig.mCname = mCname;
|
|
oldConfig.mTransmitting = mTransmitting;
|
|
oldConfig.mVideoRtxSsrcs = mVideoRtxSsrcs;
|
|
oldConfig.mVideoCodec = mVideoCodec;
|
|
oldConfig.mVideoRtpRtcpConfig = mVideoRtpRtcpConfig;
|
|
oldConfig.mVideoCodecMode = mVideoCodecMode;
|
|
|
|
VideoConfig newConfig(oldConfig);
|
|
|
|
UpdateBaseConfig(&newConfig);
|
|
|
|
newConfig.mVideoRtxSsrcs = GetJsepTransceiver().mSendTrack.GetRtxSsrcs();
|
|
|
|
const JsepTrackNegotiatedDetails details(
|
|
*GetJsepTransceiver().mSendTrack.GetNegotiatedDetails());
|
|
|
|
if (mSenderTrack) {
|
|
RefPtr<mozilla::dom::VideoStreamTrack> videotrack =
|
|
mSenderTrack->AsVideoStreamTrack();
|
|
|
|
if (!videotrack) {
|
|
MOZ_CRASH(
|
|
"In ConfigureVideoCodecMode, mSenderTrack is not video! This should "
|
|
"never happen!");
|
|
}
|
|
|
|
dom::MediaSourceEnum source = videotrack->GetSource().GetMediaSource();
|
|
switch (source) {
|
|
case dom::MediaSourceEnum::Browser:
|
|
case dom::MediaSourceEnum::Screen:
|
|
case dom::MediaSourceEnum::Window:
|
|
case dom::MediaSourceEnum::Application:
|
|
newConfig.mVideoCodecMode = webrtc::VideoCodecMode::kScreensharing;
|
|
break;
|
|
|
|
case dom::MediaSourceEnum::Camera:
|
|
case dom::MediaSourceEnum::Other:
|
|
// Other is used by canvas capture, which we treat as realtime video.
|
|
// This seems debatable, but we've been doing it this way for a long
|
|
// time, so this is likely fine.
|
|
newConfig.mVideoCodecMode = webrtc::VideoCodecMode::kRealtimeVideo;
|
|
break;
|
|
|
|
case dom::MediaSourceEnum::Microphone:
|
|
case dom::MediaSourceEnum::AudioCapture:
|
|
MOZ_ASSERT(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
std::vector<VideoCodecConfig> configs;
|
|
RTCRtpTransceiver::NegotiatedDetailsToVideoCodecConfigs(details, &configs);
|
|
|
|
if (configs.empty()) {
|
|
// TODO: Are we supposed to plumb this error back to JS? This does not
|
|
// seem like a failure to set an answer, it just means that codec
|
|
// negotiation failed. For now, we're just doing the same thing we do
|
|
// if negotiation as a whole failed.
|
|
MOZ_LOG(gSenderLog, LogLevel::Error,
|
|
("%s[%s]: %s No video codecs were negotiated (send).",
|
|
mPc->GetHandle().c_str(), GetMid().c_str(), __FUNCTION__));
|
|
return Nothing();
|
|
}
|
|
|
|
newConfig.mVideoCodec = Some(configs[0]);
|
|
// Spec says that we start using new parameters right away, _before_ we
|
|
// update the parameters that are visible to JS (ie; mParameters).
|
|
const RTCRtpSendParameters& parameters =
|
|
mPendingParameters.isSome() ? *mPendingParameters : mParameters;
|
|
for (VideoCodecConfig::Encoding& conduitEncoding :
|
|
newConfig.mVideoCodec->mEncodings) {
|
|
for (const RTCRtpEncodingParameters& jsEncoding : parameters.mEncodings) {
|
|
std::string rid;
|
|
if (jsEncoding.mRid.WasPassed()) {
|
|
rid = NS_ConvertUTF16toUTF8(jsEncoding.mRid.Value()).get();
|
|
}
|
|
if (conduitEncoding.rid == rid) {
|
|
ApplyJsEncodingToConduitEncoding(jsEncoding, &conduitEncoding);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!mHaveLoggedUlpfecInfo) {
|
|
bool ulpfecNegotiated = false;
|
|
for (const auto& codec : configs) {
|
|
if (nsCRT::strcasestr(codec.mName.c_str(), "ulpfec")) {
|
|
ulpfecNegotiated = true;
|
|
}
|
|
}
|
|
mozilla::glean::codec_stats::ulpfec_negotiated
|
|
.Get(ulpfecNegotiated ? "negotiated"_ns : "not_negotiated"_ns)
|
|
.Add(1);
|
|
mHaveLoggedUlpfecInfo = true;
|
|
}
|
|
|
|
// Log codec information we are tracking
|
|
if (!mHaveLoggedOtherFec &&
|
|
!GetJsepTransceiver().mSendTrack.GetFecCodecName().empty()) {
|
|
mozilla::glean::codec_stats::other_fec_signaled
|
|
.Get(nsDependentCString(
|
|
GetJsepTransceiver().mSendTrack.GetFecCodecName().c_str()))
|
|
.Add(1);
|
|
mHaveLoggedOtherFec = true;
|
|
}
|
|
if (!mHaveLoggedVideoPreferredCodec &&
|
|
!GetJsepTransceiver().mSendTrack.GetVideoPreferredCodec().empty()) {
|
|
mozilla::glean::codec_stats::video_preferred_codec
|
|
.Get(nsDependentCString(
|
|
GetJsepTransceiver().mSendTrack.GetVideoPreferredCodec().c_str()))
|
|
.Add(1);
|
|
mHaveLoggedVideoPreferredCodec = true;
|
|
}
|
|
|
|
newConfig.mVideoRtpRtcpConfig = Some(details.GetRtpRtcpConfig());
|
|
|
|
if (newConfig == oldConfig) {
|
|
MOZ_LOG(gSenderLog, LogLevel::Debug,
|
|
("%s[%s]: %s No change in video config", mPc->GetHandle().c_str(),
|
|
GetMid().c_str(), __FUNCTION__));
|
|
return Nothing();
|
|
}
|
|
|
|
if (newConfig.mVideoCodec.isSome()) {
|
|
MOZ_ASSERT(newConfig.mSsrcs.size() ==
|
|
newConfig.mVideoCodec->mEncodings.size());
|
|
}
|
|
return Some(newConfig);
|
|
}
|
|
|
|
Maybe<RTCRtpSender::AudioConfig> RTCRtpSender::GetNewAudioConfig() {
|
|
AudioConfig oldConfig;
|
|
oldConfig.mSsrcs = mSsrcs;
|
|
oldConfig.mLocalRtpExtensions = mLocalRtpExtensions;
|
|
oldConfig.mCname = mCname;
|
|
oldConfig.mTransmitting = mTransmitting;
|
|
oldConfig.mAudioCodec = mAudioCodec;
|
|
|
|
AudioConfig newConfig(oldConfig);
|
|
|
|
UpdateBaseConfig(&newConfig);
|
|
|
|
if (GetJsepTransceiver().mSendTrack.GetNegotiatedDetails() &&
|
|
GetJsepTransceiver().mSendTrack.GetActive()) {
|
|
const auto& details(
|
|
*GetJsepTransceiver().mSendTrack.GetNegotiatedDetails());
|
|
|
|
std::vector<AudioCodecConfig> configs;
|
|
RTCRtpTransceiver::NegotiatedDetailsToAudioCodecConfigs(details, &configs);
|
|
if (configs.empty()) {
|
|
// TODO: Are we supposed to plumb this error back to JS? This does not
|
|
// seem like a failure to set an answer, it just means that codec
|
|
// negotiation failed. For now, we're just doing the same thing we do
|
|
// if negotiation as a whole failed.
|
|
MOZ_LOG(gSenderLog, LogLevel::Error,
|
|
("%s[%s]: %s No audio codecs were negotiated (send)",
|
|
mPc->GetHandle().c_str(), GetMid().c_str(), __FUNCTION__));
|
|
return Nothing();
|
|
}
|
|
|
|
std::vector<AudioCodecConfig> dtmfConfigs;
|
|
std::copy_if(
|
|
configs.begin(), configs.end(), std::back_inserter(dtmfConfigs),
|
|
[](const auto& value) { return value.mName == "telephone-event"; });
|
|
|
|
const AudioCodecConfig& sendCodec = configs[0];
|
|
|
|
if (!dtmfConfigs.empty()) {
|
|
// There is at least one telephone-event codec.
|
|
// We primarily choose the codec whose frequency matches the send codec.
|
|
// Secondarily we choose the one with the lowest frequency.
|
|
auto dtmfIterator =
|
|
std::find_if(dtmfConfigs.begin(), dtmfConfigs.end(),
|
|
[&sendCodec](const auto& dtmfCodec) {
|
|
return dtmfCodec.mFreq == sendCodec.mFreq;
|
|
});
|
|
if (dtmfIterator == dtmfConfigs.end()) {
|
|
dtmfIterator = std::min_element(
|
|
dtmfConfigs.begin(), dtmfConfigs.end(),
|
|
[](const auto& a, const auto& b) { return a.mFreq < b.mFreq; });
|
|
}
|
|
MOZ_ASSERT(dtmfIterator != dtmfConfigs.end());
|
|
newConfig.mDtmfPt = dtmfIterator->mType;
|
|
newConfig.mDtmfFreq = dtmfIterator->mFreq;
|
|
}
|
|
|
|
newConfig.mAudioCodec = Some(sendCodec);
|
|
}
|
|
|
|
if (!mHaveLoggedAudioPreferredCodec &&
|
|
!GetJsepTransceiver().mSendTrack.GetAudioPreferredCodec().empty()) {
|
|
mozilla::glean::codec_stats::audio_preferred_codec
|
|
.Get(nsDependentCString(
|
|
GetJsepTransceiver().mSendTrack.GetAudioPreferredCodec().c_str()))
|
|
.Add(1);
|
|
mHaveLoggedAudioPreferredCodec = true;
|
|
}
|
|
|
|
if (newConfig == oldConfig) {
|
|
MOZ_LOG(gSenderLog, LogLevel::Debug,
|
|
("%s[%s]: %s No change in audio config", mPc->GetHandle().c_str(),
|
|
GetMid().c_str(), __FUNCTION__));
|
|
return Nothing();
|
|
}
|
|
|
|
return Some(newConfig);
|
|
}
|
|
|
|
void RTCRtpSender::UpdateBaseConfig(BaseConfig* aConfig) {
|
|
aConfig->mSsrcs = GetJsepTransceiver().mSendTrack.GetSsrcs();
|
|
aConfig->mCname = GetJsepTransceiver().mSendTrack.GetCNAME();
|
|
|
|
if (GetJsepTransceiver().mSendTrack.GetNegotiatedDetails() &&
|
|
GetJsepTransceiver().mSendTrack.GetActive()) {
|
|
const auto& details(
|
|
*GetJsepTransceiver().mSendTrack.GetNegotiatedDetails());
|
|
{
|
|
std::vector<webrtc::RtpExtension> extmaps;
|
|
// @@NG read extmap from track
|
|
details.ForEachRTPHeaderExtension(
|
|
[&extmaps](const SdpExtmapAttributeList::Extmap& extmap) {
|
|
extmaps.emplace_back(extmap.extensionname, extmap.entry);
|
|
});
|
|
aConfig->mLocalRtpExtensions = extmaps;
|
|
}
|
|
}
|
|
// RTCRtpTransceiver::IsSending is updated after negotiation completes, in a
|
|
// queued task (which we may be in right now). Don't use
|
|
// JsepTrack::GetActive, because that updates before the queued task, which
|
|
// is too early for some of the things we interact with here (eg;
|
|
// RTCDTMFSender).
|
|
aConfig->mTransmitting = mTransceiver->IsSending();
|
|
}
|
|
|
|
void RTCRtpSender::ApplyVideoConfig(const VideoConfig& aConfig) {
|
|
if (aConfig.mVideoCodec.isSome()) {
|
|
MOZ_ASSERT(aConfig.mSsrcs.size() == aConfig.mVideoCodec->mEncodings.size());
|
|
}
|
|
|
|
mSsrcs = aConfig.mSsrcs;
|
|
mCname = aConfig.mCname;
|
|
mLocalRtpExtensions = aConfig.mLocalRtpExtensions;
|
|
|
|
mVideoRtxSsrcs = aConfig.mVideoRtxSsrcs;
|
|
mVideoCodec = aConfig.mVideoCodec;
|
|
mVideoRtpRtcpConfig = aConfig.mVideoRtpRtcpConfig;
|
|
mVideoCodecMode = aConfig.mVideoCodecMode;
|
|
|
|
mTransmitting = aConfig.mTransmitting;
|
|
}
|
|
|
|
void RTCRtpSender::ApplyAudioConfig(const AudioConfig& aConfig) {
|
|
mSsrcs = aConfig.mSsrcs;
|
|
mCname = aConfig.mCname;
|
|
mLocalRtpExtensions = aConfig.mLocalRtpExtensions;
|
|
|
|
mAudioCodec = aConfig.mAudioCodec;
|
|
|
|
if (aConfig.mDtmfPt >= 0) {
|
|
mDtmf->SetPayloadType(aConfig.mDtmfPt, aConfig.mDtmfFreq);
|
|
}
|
|
|
|
mTransmitting = aConfig.mTransmitting;
|
|
}
|
|
|
|
void RTCRtpSender::Stop() {
|
|
MOZ_ASSERT(mTransceiver->Stopping());
|
|
mTransmitting = false;
|
|
}
|
|
|
|
bool RTCRtpSender::HasTrack(const dom::MediaStreamTrack* aTrack) const {
|
|
if (!mSenderTrack) {
|
|
return false;
|
|
}
|
|
|
|
if (!aTrack) {
|
|
return true;
|
|
}
|
|
|
|
return mSenderTrack.get() == aTrack;
|
|
}
|
|
|
|
RefPtr<MediaPipelineTransmit> RTCRtpSender::GetPipeline() const {
|
|
return mPipeline;
|
|
}
|
|
|
|
std::string RTCRtpSender::GetMid() const { return mTransceiver->GetMidAscii(); }
|
|
|
|
JsepTransceiver& RTCRtpSender::GetJsepTransceiver() {
|
|
return mTransceiver->GetJsepTransceiver();
|
|
}
|
|
|
|
void RTCRtpSender::UpdateDtmfSender() {
|
|
if (!mDtmf) {
|
|
return;
|
|
}
|
|
|
|
if (mTransmitting) {
|
|
return;
|
|
}
|
|
|
|
mDtmf->StopPlayout();
|
|
}
|
|
|
|
void RTCRtpSender::SetTransform(RTCRtpScriptTransform* aTransform,
|
|
ErrorResult& aError) {
|
|
if (aTransform == mTransform.get()) {
|
|
// Ok... smile and nod
|
|
// TODO: Depending on spec, this might throw
|
|
// https://github.com/w3c/webrtc-encoded-transform/issues/189
|
|
return;
|
|
}
|
|
|
|
if (aTransform && aTransform->IsClaimed()) {
|
|
aError.ThrowInvalidStateError("transform has already been used elsewhere");
|
|
return;
|
|
}
|
|
|
|
// Seamless switch for frames
|
|
if (aTransform) {
|
|
mFrameTransformerProxy = &aTransform->GetProxy();
|
|
} else {
|
|
mFrameTransformerProxy = nullptr;
|
|
}
|
|
|
|
if (mTransform) {
|
|
mTransform->GetProxy().SetSender(nullptr);
|
|
}
|
|
|
|
mTransform = const_cast<RTCRtpScriptTransform*>(aTransform);
|
|
|
|
if (mTransform) {
|
|
mTransform->GetProxy().SetSender(this);
|
|
mTransform->SetClaimed();
|
|
}
|
|
}
|
|
|
|
bool RTCRtpSender::GenerateKeyFrame(const Maybe<std::string>& aRid) {
|
|
if (!mTransform || !mPipeline || !mSenderTrack) {
|
|
return false;
|
|
}
|
|
|
|
mPipeline->mConduit->AsVideoSessionConduit().apply([&](const auto& conduit) {
|
|
conduit->GenerateKeyFrame(aRid, &mTransform->GetProxy());
|
|
});
|
|
return true;
|
|
}
|
|
|
|
} // namespace mozilla::dom
|
|
|
|
#undef LOGTAG
|