diff options
Diffstat (limited to 'dom/media/webrtc/jsep/JsepCodecDescription.h')
-rw-r--r-- | dom/media/webrtc/jsep/JsepCodecDescription.h | 1134 |
1 files changed, 1134 insertions, 0 deletions
diff --git a/dom/media/webrtc/jsep/JsepCodecDescription.h b/dom/media/webrtc/jsep/JsepCodecDescription.h new file mode 100644 index 0000000000..1365785780 --- /dev/null +++ b/dom/media/webrtc/jsep/JsepCodecDescription.h @@ -0,0 +1,1134 @@ +/* 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/. */ + +#ifndef _JSEPCODECDESCRIPTION_H_ +#define _JSEPCODECDESCRIPTION_H_ + +#include <cmath> +#include <string> +#include "sdp/SdpMediaSection.h" +#include "sdp/SdpHelper.h" +#include "nsCRT.h" +#include "nsString.h" +#include "mozilla/net/DataChannelProtocol.h" +#include "mozilla/Preferences.h" + +namespace mozilla { + +#define JSEP_CODEC_CLONE(T) \ + virtual JsepCodecDescription* Clone() const override { return new T(*this); } + +// A single entry in our list of known codecs. +class JsepCodecDescription { + public: + JsepCodecDescription(const std::string& defaultPt, const std::string& name, + uint32_t clock, uint32_t channels, bool enabled) + : mDefaultPt(defaultPt), + mName(name), + mClock(clock), + mChannels(channels), + mEnabled(enabled), + mStronglyPreferred(false), + mDirection(sdp::kSend) {} + virtual ~JsepCodecDescription() {} + + virtual SdpMediaSection::MediaType Type() const = 0; + + virtual JsepCodecDescription* Clone() const = 0; + + bool GetPtAsInt(uint16_t* ptOutparam) const { + return SdpHelper::GetPtAsInt(mDefaultPt, ptOutparam); + } + + // The id used for codec stats, to uniquely identify this codec configuration + // within a transport. + const nsString& StatsId() const { + if (!mStatsId) { + mStatsId.emplace(); + mStatsId->AppendPrintf( + "_%s_%s/%s_%u_%u_%s", mDefaultPt.c_str(), + Type() == SdpMediaSection::kVideo ? "video" : "audio", mName.c_str(), + mClock, mChannels, mSdpFmtpLine ? mSdpFmtpLine->c_str() : "nothing"); + } + return *mStatsId; + } + + virtual bool Matches(const std::string& fmt, + const SdpMediaSection& remoteMsection) const { + // note: fmt here is remote fmt (to go with remoteMsection) + if (Type() != remoteMsection.GetMediaType()) { + return false; + } + + const SdpRtpmapAttributeList::Rtpmap* entry(remoteMsection.FindRtpmap(fmt)); + + if (entry) { + if (!nsCRT::strcasecmp(mName.c_str(), entry->name.c_str()) && + (mClock == entry->clock) && (mChannels == entry->channels)) { + return ParametersMatch(fmt, remoteMsection); + } + } else if (!fmt.compare("9") && mName == "G722") { + return true; + } else if (!fmt.compare("0") && mName == "PCMU") { + return true; + } else if (!fmt.compare("8") && mName == "PCMA") { + return true; + } + return false; + } + + virtual bool ParametersMatch(const std::string& fmt, + const SdpMediaSection& remoteMsection) const { + return true; + } + + Maybe<std::string> GetMatchingFormat( + const SdpMediaSection& remoteMsection) const { + for (const auto& fmt : remoteMsection.GetFormats()) { + if (Matches(fmt, remoteMsection)) { + return Some(fmt); + } + } + return Nothing(); + } + + virtual bool Negotiate(const std::string& pt, + const SdpMediaSection& remoteMsection, + bool remoteIsOffer, + Maybe<const SdpMediaSection&> localMsection) { + // Configuration might change. Invalidate the stats id. + mStatsId = Nothing(); + if (mDirection == sdp::kSend || remoteIsOffer) { + mDefaultPt = pt; + } + if (localMsection) { + // Offer/answer is concluding. Update the sdpFmtpLine. + MOZ_ASSERT(mDirection == sdp::kSend || mDirection == sdp::kRecv); + const SdpMediaSection& msection = + mDirection == sdp::kSend ? remoteMsection : *localMsection; + UpdateSdpFmtpLine(ToMaybeRef(msection.FindFmtp(mDefaultPt))); + } + return true; + } + + virtual void AddToMediaSection(SdpMediaSection& msection) const { + if (mEnabled && msection.GetMediaType() == Type()) { + if (mDirection == sdp::kRecv) { + msection.AddCodec(mDefaultPt, mName, mClock, mChannels); + } + + AddParametersToMSection(msection); + } + } + + virtual void AddParametersToMSection(SdpMediaSection& msection) const {} + + virtual void EnsureNoDuplicatePayloadTypes(std::set<std::string>& aUsedPts) { + mEnabled = EnsurePayloadTypeNotDuplicate(aUsedPts, mDefaultPt); + } + + bool EnsurePayloadTypeNotDuplicate(std::set<std::string>& aUsedPts, + std::string& aPtToCheck) { + if (!mEnabled) { + return false; + } + + if (!aUsedPts.count(aPtToCheck)) { + aUsedPts.insert(aPtToCheck); + return true; + } + + // |codec| cannot use its current payload type. Try to find another. + for (uint16_t freePt = 96; freePt <= 127; ++freePt) { + // Not super efficient, but readability is probably more important. + std::ostringstream os; + os << freePt; + std::string freePtAsString = os.str(); + + if (!aUsedPts.count(freePtAsString)) { + aUsedPts.insert(freePtAsString); + aPtToCheck = freePtAsString; + return true; + } + } + + return false; + } + + // TODO Bug 1751671: Take a verbatim fmtp line (std::string or eq.) instead + // of fmtp parameters that have to be (re-)serialized. + void UpdateSdpFmtpLine( + const Maybe<const SdpFmtpAttributeList::Parameters&> aParams) { + mSdpFmtpLine = aParams.map([](const auto& aFmtp) { + std::stringstream ss; + aFmtp.Serialize(ss); + return ss.str(); + }); + } + + std::string mDefaultPt; + std::string mName; + Maybe<std::string> mSdpFmtpLine; + mutable Maybe<nsString> mStatsId; + uint32_t mClock; + uint32_t mChannels; + bool mEnabled; + bool mStronglyPreferred; + sdp::Direction mDirection; + // Will hold constraints from both fmtp and rid + EncodingConstraints mConstraints; +}; + +class JsepAudioCodecDescription : public JsepCodecDescription { + public: + JsepAudioCodecDescription(const std::string& defaultPt, + const std::string& name, uint32_t clock, + uint32_t channels, bool enabled = true) + : JsepCodecDescription(defaultPt, name, clock, channels, enabled), + mMaxPlaybackRate(0), + mForceMono(false), + mFECEnabled(false), + mDtmfEnabled(false), + mMaxAverageBitrate(0), + mDTXEnabled(false), + mFrameSizeMs(0), + mMinFrameSizeMs(0), + mMaxFrameSizeMs(0), + mCbrEnabled(false) {} + + static constexpr SdpMediaSection::MediaType type = SdpMediaSection::kAudio; + + SdpMediaSection::MediaType Type() const override { return type; } + + JSEP_CODEC_CLONE(JsepAudioCodecDescription) + + static UniquePtr<JsepAudioCodecDescription> CreateDefaultOpus() { + // Per jmspeex on IRC: + // For 32KHz sampling, 28 is ok, 32 is good, 40 should be really good + // quality. Note that 1-2Kbps will be wasted on a stereo Opus channel + // with mono input compared to configuring it for mono. + // If we reduce bitrate enough Opus will low-pass us; 16000 will kill a + // 9KHz tone. This should be adaptive when we're at the low-end of video + // bandwidth (say <100Kbps), and if we're audio-only, down to 8 or + // 12Kbps. + return MakeUnique<JsepAudioCodecDescription>("109", "opus", 48000, 2); + } + + static UniquePtr<JsepAudioCodecDescription> CreateDefaultG722() { + return MakeUnique<JsepAudioCodecDescription>("9", "G722", 8000, 1); + } + + static UniquePtr<JsepAudioCodecDescription> CreateDefaultPCMU() { + return MakeUnique<JsepAudioCodecDescription>("0", "PCMU", 8000, 1); + } + + static UniquePtr<JsepAudioCodecDescription> CreateDefaultPCMA() { + return MakeUnique<JsepAudioCodecDescription>("8", "PCMA", 8000, 1); + } + + static UniquePtr<JsepAudioCodecDescription> CreateDefaultTelephoneEvent() { + return MakeUnique<JsepAudioCodecDescription>("101", "telephone-event", 8000, + 1); + } + + SdpFmtpAttributeList::OpusParameters GetOpusParameters( + const std::string& pt, const SdpMediaSection& msection) const { + // Will contain defaults if nothing else + SdpFmtpAttributeList::OpusParameters result; + auto* params = msection.FindFmtp(pt); + + if (params && params->codec_type == SdpRtpmapAttributeList::kOpus) { + result = + static_cast<const SdpFmtpAttributeList::OpusParameters&>(*params); + } + + return result; + } + + SdpFmtpAttributeList::TelephoneEventParameters GetTelephoneEventParameters( + const std::string& pt, const SdpMediaSection& msection) const { + // Will contain defaults if nothing else + SdpFmtpAttributeList::TelephoneEventParameters result; + auto* params = msection.FindFmtp(pt); + + if (params && + params->codec_type == SdpRtpmapAttributeList::kTelephoneEvent) { + result = + static_cast<const SdpFmtpAttributeList::TelephoneEventParameters&>( + *params); + } + + return result; + } + + void AddParametersToMSection(SdpMediaSection& msection) const override { + if (mDirection == sdp::kSend) { + return; + } + + if (mName == "opus") { + SdpFmtpAttributeList::OpusParameters opusParams( + GetOpusParameters(mDefaultPt, msection)); + if (mMaxPlaybackRate) { + opusParams.maxplaybackrate = mMaxPlaybackRate; + } + opusParams.maxAverageBitrate = mMaxAverageBitrate; + + if (mChannels == 2 && + !Preferences::GetBool("media.peerconnection.sdp.disable_stereo_fmtp", + false) && + !mForceMono) { + // We prefer to receive stereo, if available. + opusParams.stereo = 1; + } + opusParams.useInBandFec = mFECEnabled ? 1 : 0; + opusParams.useDTX = mDTXEnabled; + opusParams.frameSizeMs = mFrameSizeMs; + opusParams.minFrameSizeMs = mMinFrameSizeMs; + opusParams.maxFrameSizeMs = mMaxFrameSizeMs; + opusParams.useCbr = mCbrEnabled; + msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, opusParams)); + } else if (mName == "telephone-event") { + // add the default dtmf tones + SdpFmtpAttributeList::TelephoneEventParameters teParams( + GetTelephoneEventParameters(mDefaultPt, msection)); + msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, teParams)); + } + } + + bool Negotiate(const std::string& pt, const SdpMediaSection& remoteMsection, + bool remoteIsOffer, + Maybe<const SdpMediaSection&> localMsection) override { + JsepCodecDescription::Negotiate(pt, remoteMsection, remoteIsOffer, + localMsection); + if (mName == "opus" && mDirection == sdp::kSend) { + SdpFmtpAttributeList::OpusParameters opusParams( + GetOpusParameters(mDefaultPt, remoteMsection)); + + mMaxPlaybackRate = opusParams.maxplaybackrate; + mForceMono = !opusParams.stereo; + // draft-ietf-rtcweb-fec-03.txt section 4.2 says support for FEC + // at the received side is declarative and can be negotiated + // separately for either media direction. + mFECEnabled = opusParams.useInBandFec; + if ((opusParams.maxAverageBitrate >= 6000) && + (opusParams.maxAverageBitrate <= 510000)) { + mMaxAverageBitrate = opusParams.maxAverageBitrate; + } + mDTXEnabled = opusParams.useDTX; + if (remoteMsection.GetAttributeList().HasAttribute( + SdpAttribute::kPtimeAttribute)) { + mFrameSizeMs = remoteMsection.GetAttributeList().GetPtime(); + } else { + mFrameSizeMs = opusParams.frameSizeMs; + } + mMinFrameSizeMs = opusParams.minFrameSizeMs; + if (remoteMsection.GetAttributeList().HasAttribute( + SdpAttribute::kMaxptimeAttribute)) { + mMaxFrameSizeMs = remoteMsection.GetAttributeList().GetMaxptime(); + } else { + mMaxFrameSizeMs = opusParams.maxFrameSizeMs; + } + mCbrEnabled = opusParams.useCbr; + } + + return true; + } + + uint32_t mMaxPlaybackRate; + bool mForceMono; + bool mFECEnabled; + bool mDtmfEnabled; + uint32_t mMaxAverageBitrate; + bool mDTXEnabled; + uint32_t mFrameSizeMs; + uint32_t mMinFrameSizeMs; + uint32_t mMaxFrameSizeMs; + bool mCbrEnabled; +}; + +class JsepVideoCodecDescription : public JsepCodecDescription { + public: + JsepVideoCodecDescription(const std::string& defaultPt, + const std::string& name, uint32_t clock, + bool enabled = true) + : JsepCodecDescription(defaultPt, name, clock, 0, enabled), + mTmmbrEnabled(false), + mRembEnabled(false), + mFECEnabled(false), + mTransportCCEnabled(false), + mRtxEnabled(false), + mProfileLevelId(0), + mPacketizationMode(0) { + // Add supported rtcp-fb types + mNackFbTypes.push_back(""); + mNackFbTypes.push_back(SdpRtcpFbAttributeList::pli); + mCcmFbTypes.push_back(SdpRtcpFbAttributeList::fir); + } + + static constexpr SdpMediaSection::MediaType type = SdpMediaSection::kVideo; + + SdpMediaSection::MediaType Type() const override { return type; } + + static UniquePtr<JsepVideoCodecDescription> CreateDefaultVP8(bool aUseRtx) { + auto codec = MakeUnique<JsepVideoCodecDescription>("120", "VP8", 90000); + // Defaults for mandatory params + codec->mConstraints.maxFs = 12288; // Enough for 2048x1536 + codec->mConstraints.maxFps = Some(60); + if (aUseRtx) { + codec->EnableRtx("124"); + } + return codec; + } + + static UniquePtr<JsepVideoCodecDescription> CreateDefaultVP9(bool aUseRtx) { + auto codec = MakeUnique<JsepVideoCodecDescription>("121", "VP9", 90000); + // Defaults for mandatory params + codec->mConstraints.maxFs = 12288; // Enough for 2048x1536 + codec->mConstraints.maxFps = Some(60); + if (aUseRtx) { + codec->EnableRtx("125"); + } + return codec; + } + + static UniquePtr<JsepVideoCodecDescription> CreateDefaultH264_0( + bool aUseRtx) { + auto codec = MakeUnique<JsepVideoCodecDescription>("97", "H264", 90000); + codec->mPacketizationMode = 0; + // Defaults for mandatory params + codec->mProfileLevelId = 0x42E00D; + if (aUseRtx) { + codec->EnableRtx("98"); + } + return codec; + } + + static UniquePtr<JsepVideoCodecDescription> CreateDefaultH264_1( + bool aUseRtx) { + auto codec = MakeUnique<JsepVideoCodecDescription>("126", "H264", 90000); + codec->mPacketizationMode = 1; + // Defaults for mandatory params + codec->mProfileLevelId = 0x42E00D; + if (aUseRtx) { + codec->EnableRtx("127"); + } + return codec; + } + + static UniquePtr<JsepVideoCodecDescription> CreateDefaultUlpFec() { + return MakeUnique<JsepVideoCodecDescription>( + "123", // payload type + "ulpfec", // codec name + 90000 // clock rate (match other video codecs) + ); + } + + static UniquePtr<JsepVideoCodecDescription> CreateDefaultRed() { + return MakeUnique<JsepVideoCodecDescription>( + "122", // payload type + "red", // codec name + 90000 // clock rate (match other video codecs) + ); + } + + virtual void EnableTmmbr() { + // EnableTmmbr can be called multiple times due to multiple calls to + // PeerConnectionImpl::ConfigureJsepSessionCodecs + if (!mTmmbrEnabled) { + mTmmbrEnabled = true; + mCcmFbTypes.push_back(SdpRtcpFbAttributeList::tmmbr); + } + } + + virtual void EnableRemb() { + // EnableRemb can be called multiple times due to multiple calls to + // PeerConnectionImpl::ConfigureJsepSessionCodecs + if (!mRembEnabled) { + mRembEnabled = true; + mOtherFbTypes.push_back({"", SdpRtcpFbAttributeList::kRemb, "", ""}); + } + } + + virtual void EnableFec(std::string redPayloadType, + std::string ulpfecPayloadType) { + // Enabling FEC for video works a little differently than enabling + // REMB or TMMBR. Support for FEC is indicated by the presence of + // particular codes (red and ulpfec) instead of using rtcpfb + // attributes on a given codec. There is no rtcpfb to push for FEC + // as can be seen above when REMB or TMMBR are enabled. + + // Ensure we have valid payload types. This returns zero on failure, which + // is a valid payload type. + uint16_t redPt, ulpfecPt; + if (!SdpHelper::GetPtAsInt(redPayloadType, &redPt) || + !SdpHelper::GetPtAsInt(ulpfecPayloadType, &ulpfecPt)) { + return; + } + + mFECEnabled = true; + mREDPayloadType = redPayloadType; + mULPFECPayloadType = ulpfecPayloadType; + } + + virtual void EnableTransportCC() { + if (!mTransportCCEnabled) { + mTransportCCEnabled = true; + mOtherFbTypes.push_back( + {"", SdpRtcpFbAttributeList::kTransportCC, "", ""}); + } + } + + void EnableRtx(const std::string& rtxPayloadType) { + mRtxEnabled = true; + mRtxPayloadType = rtxPayloadType; + } + + void AddParametersToMSection(SdpMediaSection& msection) const override { + AddFmtpsToMSection(msection); + AddRtcpFbsToMSection(msection); + } + + void AddFmtpsToMSection(SdpMediaSection& msection) const { + if (mName == "H264") { + SdpFmtpAttributeList::H264Parameters h264Params( + GetH264Parameters(mDefaultPt, msection)); + + if (mDirection == sdp::kSend) { + if (!h264Params.level_asymmetry_allowed) { + // First time the fmtp has been set; set just in case this is for a + // sendonly m-line, since even though we aren't receiving the level + // negotiation still needs to happen (sigh). + h264Params.profile_level_id = mProfileLevelId; + } + } else { + // Parameters that only apply to what we receive + h264Params.max_mbps = mConstraints.maxMbps; + h264Params.max_fs = mConstraints.maxFs; + h264Params.max_cpb = mConstraints.maxCpb; + h264Params.max_dpb = mConstraints.maxDpb; + h264Params.max_br = mConstraints.maxBr; + strncpy(h264Params.sprop_parameter_sets, mSpropParameterSets.c_str(), + sizeof(h264Params.sprop_parameter_sets) - 1); + h264Params.profile_level_id = mProfileLevelId; + } + + // Parameters that apply to both the send and recv directions + h264Params.packetization_mode = mPacketizationMode; + // Hard-coded, may need to change someday? + h264Params.level_asymmetry_allowed = true; + + msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, h264Params)); + } else if (mName == "red" && !mRedundantEncodings.empty()) { + SdpFmtpAttributeList::RedParameters redParams( + GetRedParameters(mDefaultPt, msection)); + redParams.encodings = mRedundantEncodings; + msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, redParams)); + } else if (mName == "VP8" || mName == "VP9") { + if (mDirection == sdp::kRecv) { + // VP8 and VP9 share the same SDP parameters thus far + SdpFmtpAttributeList::VP8Parameters vp8Params( + GetVP8Parameters(mDefaultPt, msection)); + + vp8Params.max_fs = mConstraints.maxFs; + if (mConstraints.maxFps.isSome()) { + vp8Params.max_fr = + static_cast<unsigned int>(std::round(*mConstraints.maxFps)); + } else { + vp8Params.max_fr = 60; + } + msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, vp8Params)); + } + } + + if (mRtxEnabled && mDirection == sdp::kRecv) { + SdpFmtpAttributeList::RtxParameters params( + GetRtxParameters(mDefaultPt, msection)); + uint16_t apt; + if (SdpHelper::GetPtAsInt(mDefaultPt, &apt)) { + if (apt <= 127) { + msection.AddCodec(mRtxPayloadType, "rtx", mClock, mChannels); + + params.apt = apt; + msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mRtxPayloadType, params)); + } + } + } + } + + void AddRtcpFbsToMSection(SdpMediaSection& msection) const { + SdpRtcpFbAttributeList rtcpfbs(msection.GetRtcpFbs()); + for (const auto& rtcpfb : rtcpfbs.mFeedbacks) { + if (rtcpfb.pt == mDefaultPt) { + // Already set by the codec for the other direction. + return; + } + } + + for (const std::string& type : mAckFbTypes) { + rtcpfbs.PushEntry(mDefaultPt, SdpRtcpFbAttributeList::kAck, type); + } + for (const std::string& type : mNackFbTypes) { + rtcpfbs.PushEntry(mDefaultPt, SdpRtcpFbAttributeList::kNack, type); + } + for (const std::string& type : mCcmFbTypes) { + rtcpfbs.PushEntry(mDefaultPt, SdpRtcpFbAttributeList::kCcm, type); + } + for (const auto& fb : mOtherFbTypes) { + rtcpfbs.PushEntry(mDefaultPt, fb.type, fb.parameter, fb.extra); + } + + msection.SetRtcpFbs(rtcpfbs); + } + + SdpFmtpAttributeList::H264Parameters GetH264Parameters( + const std::string& pt, const SdpMediaSection& msection) const { + // Will contain defaults if nothing else + SdpFmtpAttributeList::H264Parameters result; + auto* params = msection.FindFmtp(pt); + + if (params && params->codec_type == SdpRtpmapAttributeList::kH264) { + result = + static_cast<const SdpFmtpAttributeList::H264Parameters&>(*params); + } + + return result; + } + + SdpFmtpAttributeList::RedParameters GetRedParameters( + const std::string& pt, const SdpMediaSection& msection) const { + SdpFmtpAttributeList::RedParameters result; + auto* params = msection.FindFmtp(pt); + + if (params && params->codec_type == SdpRtpmapAttributeList::kRed) { + result = static_cast<const SdpFmtpAttributeList::RedParameters&>(*params); + } + + return result; + } + + SdpFmtpAttributeList::RtxParameters GetRtxParameters( + const std::string& pt, const SdpMediaSection& msection) const { + SdpFmtpAttributeList::RtxParameters result; + const auto* params = msection.FindFmtp(pt); + + if (params && params->codec_type == SdpRtpmapAttributeList::kRtx) { + result = static_cast<const SdpFmtpAttributeList::RtxParameters&>(*params); + } + + return result; + } + + Maybe<std::string> GetRtxPtByApt(const std::string& apt, + const SdpMediaSection& msection) const { + Maybe<std::string> result; + uint16_t aptAsInt; + if (!SdpHelper::GetPtAsInt(apt, &aptAsInt)) { + return result; + } + + const SdpAttributeList& attrs = msection.GetAttributeList(); + if (attrs.HasAttribute(SdpAttribute::kFmtpAttribute)) { + for (const auto& fmtpAttr : attrs.GetFmtp().mFmtps) { + if (fmtpAttr.parameters) { + auto* params = fmtpAttr.parameters.get(); + if (params && params->codec_type == SdpRtpmapAttributeList::kRtx) { + const SdpFmtpAttributeList::RtxParameters* rtxParams = + static_cast<const SdpFmtpAttributeList::RtxParameters*>(params); + if (rtxParams->apt == aptAsInt) { + result = Some(fmtpAttr.format); + break; + } + } + } + } + } + return result; + } + + SdpFmtpAttributeList::VP8Parameters GetVP8Parameters( + const std::string& pt, const SdpMediaSection& msection) const { + SdpRtpmapAttributeList::CodecType expectedType( + mName == "VP8" ? SdpRtpmapAttributeList::kVP8 + : SdpRtpmapAttributeList::kVP9); + + // Will contain defaults if nothing else + SdpFmtpAttributeList::VP8Parameters result(expectedType); + auto* params = msection.FindFmtp(pt); + + if (params && params->codec_type == expectedType) { + result = static_cast<const SdpFmtpAttributeList::VP8Parameters&>(*params); + } + + return result; + } + + void NegotiateRtcpFb(const SdpMediaSection& remoteMsection, + SdpRtcpFbAttributeList::Type type, + std::vector<std::string>* supportedTypes) { + Maybe<std::string> remoteFmt = GetMatchingFormat(remoteMsection); + if (!remoteFmt) { + return; + } + std::vector<std::string> temp; + for (auto& subType : *supportedTypes) { + if (remoteMsection.HasRtcpFb(*remoteFmt, type, subType)) { + temp.push_back(subType); + } + } + *supportedTypes = temp; + } + + void NegotiateRtcpFb( + const SdpMediaSection& remoteMsection, + std::vector<SdpRtcpFbAttributeList::Feedback>* supportedFbs) { + Maybe<std::string> remoteFmt = GetMatchingFormat(remoteMsection); + if (!remoteFmt) { + return; + } + std::vector<SdpRtcpFbAttributeList::Feedback> temp; + for (auto& fb : *supportedFbs) { + if (remoteMsection.HasRtcpFb(*remoteFmt, fb.type, fb.parameter)) { + temp.push_back(fb); + } + } + *supportedFbs = temp; + } + + void NegotiateRtcpFb(const SdpMediaSection& remote) { + // Removes rtcp-fb types that the other side doesn't support + NegotiateRtcpFb(remote, SdpRtcpFbAttributeList::kAck, &mAckFbTypes); + NegotiateRtcpFb(remote, SdpRtcpFbAttributeList::kNack, &mNackFbTypes); + NegotiateRtcpFb(remote, SdpRtcpFbAttributeList::kCcm, &mCcmFbTypes); + NegotiateRtcpFb(remote, &mOtherFbTypes); + } + + virtual bool Negotiate(const std::string& pt, + const SdpMediaSection& remoteMsection, + bool remoteIsOffer, + Maybe<const SdpMediaSection&> localMsection) override { + JsepCodecDescription::Negotiate(pt, remoteMsection, remoteIsOffer, + localMsection); + if (mName == "H264") { + SdpFmtpAttributeList::H264Parameters h264Params( + GetH264Parameters(mDefaultPt, remoteMsection)); + + // Level is negotiated symmetrically if level asymmetry is disallowed + if (!h264Params.level_asymmetry_allowed) { + SetSaneH264Level(std::min(GetSaneH264Level(h264Params.profile_level_id), + GetSaneH264Level(mProfileLevelId)), + &mProfileLevelId); + } + + if (mDirection == sdp::kSend) { + // Remote values of these apply only to the send codec. + mConstraints.maxFs = h264Params.max_fs; + mConstraints.maxMbps = h264Params.max_mbps; + mConstraints.maxCpb = h264Params.max_cpb; + mConstraints.maxDpb = h264Params.max_dpb; + mConstraints.maxBr = h264Params.max_br; + mSpropParameterSets = h264Params.sprop_parameter_sets; + // Only do this if we didn't symmetrically negotiate above + if (h264Params.level_asymmetry_allowed) { + SetSaneH264Level(GetSaneH264Level(h264Params.profile_level_id), + &mProfileLevelId); + } + } else { + // TODO(bug 1143709): max-recv-level support + } + } else if (mName == "red") { + SdpFmtpAttributeList::RedParameters redParams( + GetRedParameters(mDefaultPt, remoteMsection)); + mRedundantEncodings = redParams.encodings; + } else if (mName == "VP8" || mName == "VP9") { + if (mDirection == sdp::kSend) { + SdpFmtpAttributeList::VP8Parameters vp8Params( + GetVP8Parameters(mDefaultPt, remoteMsection)); + + mConstraints.maxFs = vp8Params.max_fs; + // Right now, we treat max-fr=0 (or the absence of max-fr) as no limit. + // We will eventually want to stop doing this (bug 1762600). + if (vp8Params.max_fr) { + mConstraints.maxFps = Some(vp8Params.max_fr); + } + } + } + + if (mRtxEnabled && (mDirection == sdp::kSend || remoteIsOffer)) { + Maybe<std::string> rtxPt = GetRtxPtByApt(mDefaultPt, remoteMsection); + if (rtxPt.isSome()) { + EnableRtx(*rtxPt); + } else { + mRtxEnabled = false; + mRtxPayloadType = ""; + } + } + + NegotiateRtcpFb(remoteMsection); + return true; + } + + // Maps the not-so-sane encoding of H264 level into something that is + // ordered in the way one would expect + // 1b is 0xAB, everything else is the level left-shifted one half-byte + // (eg; 1.0 is 0xA0, 1.1 is 0xB0, 3.1 is 0x1F0) + static uint32_t GetSaneH264Level(uint32_t profileLevelId) { + uint32_t profileIdc = (profileLevelId >> 16); + + if (profileIdc == 0x42 || profileIdc == 0x4D || profileIdc == 0x58) { + if ((profileLevelId & 0x10FF) == 0x100B) { + // Level 1b + return 0xAB; + } + } + + uint32_t level = profileLevelId & 0xFF; + + if (level == 0x09) { + // Another way to encode level 1b + return 0xAB; + } + + return level << 4; + } + + static void SetSaneH264Level(uint32_t level, uint32_t* profileLevelId) { + uint32_t profileIdc = (*profileLevelId >> 16); + uint32_t levelMask = 0xFF; + + if (profileIdc == 0x42 || profileIdc == 0x4d || profileIdc == 0x58) { + levelMask = 0x10FF; + if (level == 0xAB) { + // Level 1b + level = 0x100B; + } else { + // Not 1b, just shift + level = level >> 4; + } + } else if (level == 0xAB) { + // Another way to encode 1b + level = 0x09; + } else { + // Not 1b, just shift + level = level >> 4; + } + + *profileLevelId = (*profileLevelId & ~levelMask) | level; + } + + enum Subprofile { + kH264ConstrainedBaseline, + kH264Baseline, + kH264Main, + kH264Extended, + kH264High, + kH264High10, + kH264High42, + kH264High44, + kH264High10I, + kH264High42I, + kH264High44I, + kH264CALVC44, + kH264UnknownSubprofile + }; + + static Subprofile GetSubprofile(uint32_t profileLevelId) { + // Based on Table 5 from RFC 6184: + // Profile profile_idc profile-iop + // (hexadecimal) (binary) + + // CB 42 (B) x1xx0000 + // same as: 4D (M) 1xxx0000 + // same as: 58 (E) 11xx0000 + // B 42 (B) x0xx0000 + // same as: 58 (E) 10xx0000 + // M 4D (M) 0x0x0000 + // E 58 00xx0000 + // H 64 00000000 + // H10 6E 00000000 + // H42 7A 00000000 + // H44 F4 00000000 + // H10I 6E 00010000 + // H42I 7A 00010000 + // H44I F4 00010000 + // C44I 2C 00010000 + + if ((profileLevelId & 0xFF4F00) == 0x424000) { + // 01001111 (mask, 0x4F) + // x1xx0000 (from table) + // 01000000 (expected value, 0x40) + return kH264ConstrainedBaseline; + } + + if ((profileLevelId & 0xFF8F00) == 0x4D8000) { + // 10001111 (mask, 0x8F) + // 1xxx0000 (from table) + // 10000000 (expected value, 0x80) + return kH264ConstrainedBaseline; + } + + if ((profileLevelId & 0xFFCF00) == 0x58C000) { + // 11001111 (mask, 0xCF) + // 11xx0000 (from table) + // 11000000 (expected value, 0xC0) + return kH264ConstrainedBaseline; + } + + if ((profileLevelId & 0xFF4F00) == 0x420000) { + // 01001111 (mask, 0x4F) + // x0xx0000 (from table) + // 00000000 (expected value) + return kH264Baseline; + } + + if ((profileLevelId & 0xFFCF00) == 0x588000) { + // 11001111 (mask, 0xCF) + // 10xx0000 (from table) + // 10000000 (expected value, 0x80) + return kH264Baseline; + } + + if ((profileLevelId & 0xFFAF00) == 0x4D0000) { + // 10101111 (mask, 0xAF) + // 0x0x0000 (from table) + // 00000000 (expected value) + return kH264Main; + } + + if ((profileLevelId & 0xFF0000) == 0x580000) { + // 11001111 (mask, 0xCF) + // 00xx0000 (from table) + // 00000000 (expected value) + return kH264Extended; + } + + if ((profileLevelId & 0xFFFF00) == 0x640000) { + return kH264High; + } + + if ((profileLevelId & 0xFFFF00) == 0x6E0000) { + return kH264High10; + } + + if ((profileLevelId & 0xFFFF00) == 0x7A0000) { + return kH264High42; + } + + if ((profileLevelId & 0xFFFF00) == 0xF40000) { + return kH264High44; + } + + if ((profileLevelId & 0xFFFF00) == 0x6E1000) { + return kH264High10I; + } + + if ((profileLevelId & 0xFFFF00) == 0x7A1000) { + return kH264High42I; + } + + if ((profileLevelId & 0xFFFF00) == 0xF41000) { + return kH264High44I; + } + + if ((profileLevelId & 0xFFFF00) == 0x2C1000) { + return kH264CALVC44; + } + + return kH264UnknownSubprofile; + } + + virtual bool ParametersMatch( + const std::string& fmt, + const SdpMediaSection& remoteMsection) const override { + if (mName == "H264") { + SdpFmtpAttributeList::H264Parameters h264Params( + GetH264Parameters(fmt, remoteMsection)); + + if (h264Params.packetization_mode != mPacketizationMode) { + return false; + } + + if (GetSubprofile(h264Params.profile_level_id) != + GetSubprofile(mProfileLevelId)) { + return false; + } + } + + return true; + } + + virtual bool RtcpFbRembIsSet() const { + for (const auto& fb : mOtherFbTypes) { + if (fb.type == SdpRtcpFbAttributeList::kRemb) { + return true; + } + } + return false; + } + + virtual bool RtcpFbTransportCCIsSet() const { + for (const auto& fb : mOtherFbTypes) { + if (fb.type == SdpRtcpFbAttributeList::kTransportCC) { + return true; + } + } + return false; + } + + virtual void UpdateRedundantEncodings( + const std::vector<UniquePtr<JsepCodecDescription>>& codecs) { + for (const auto& codec : codecs) { + if (codec->Type() == type && codec->mEnabled && codec->mName != "red") { + uint16_t pt; + if (!SdpHelper::GetPtAsInt(codec->mDefaultPt, &pt)) { + continue; + } + mRedundantEncodings.push_back(pt); + } + } + } + + void EnsureNoDuplicatePayloadTypes(std::set<std::string>& aUsedPts) override { + JsepCodecDescription::EnsureNoDuplicatePayloadTypes(aUsedPts); + if (mFECEnabled) { + mFECEnabled = EnsurePayloadTypeNotDuplicate(aUsedPts, mREDPayloadType) && + EnsurePayloadTypeNotDuplicate(aUsedPts, mULPFECPayloadType); + } + if (mRtxEnabled) { + mRtxEnabled = EnsurePayloadTypeNotDuplicate(aUsedPts, mRtxPayloadType); + } + } + + JSEP_CODEC_CLONE(JsepVideoCodecDescription) + + std::vector<std::string> mAckFbTypes; + std::vector<std::string> mNackFbTypes; + std::vector<std::string> mCcmFbTypes; + std::vector<SdpRtcpFbAttributeList::Feedback> mOtherFbTypes; + bool mTmmbrEnabled; + bool mRembEnabled; + bool mFECEnabled; + bool mTransportCCEnabled; + bool mRtxEnabled; + std::string mREDPayloadType; + std::string mULPFECPayloadType; + std::string mRtxPayloadType; + std::vector<uint8_t> mRedundantEncodings; + + // H264-specific stuff + uint32_t mProfileLevelId; + uint32_t mPacketizationMode; + std::string mSpropParameterSets; +}; + +class JsepApplicationCodecDescription : public JsepCodecDescription { + // This is the new draft-21 implementation + public: + JsepApplicationCodecDescription(const std::string& name, uint16_t channels, + uint16_t localPort, + uint32_t localMaxMessageSize, + bool enabled = true) + : JsepCodecDescription("", name, 0, channels, enabled), + mLocalPort(localPort), + mLocalMaxMessageSize(localMaxMessageSize), + mRemotePort(0), + mRemoteMaxMessageSize(0), + mRemoteMMSSet(false) {} + + static constexpr SdpMediaSection::MediaType type = + SdpMediaSection::kApplication; + + SdpMediaSection::MediaType Type() const override { return type; } + + JSEP_CODEC_CLONE(JsepApplicationCodecDescription) + + static UniquePtr<JsepApplicationCodecDescription> CreateDefault() { + return MakeUnique<JsepApplicationCodecDescription>( + "webrtc-datachannel", WEBRTC_DATACHANNEL_STREAMS_DEFAULT, + WEBRTC_DATACHANNEL_PORT_DEFAULT, + WEBRTC_DATACHANNEL_MAX_MESSAGE_SIZE_LOCAL); + } + + // Override, uses sctpport or sctpmap instead of rtpmap + virtual bool Matches(const std::string& fmt, + const SdpMediaSection& remoteMsection) const override { + if (type != remoteMsection.GetMediaType()) { + return false; + } + + int sctp_port = remoteMsection.GetSctpPort(); + bool fmt_matches = + nsCRT::strcasecmp(mName.c_str(), + remoteMsection.GetFormats()[0].c_str()) == 0; + if (sctp_port && fmt_matches) { + // New sctp draft 21 format + return true; + } + + const SdpSctpmapAttributeList::Sctpmap* sctp_map( + remoteMsection.GetSctpmap()); + if (sctp_map) { + // Old sctp draft 05 format + return nsCRT::strcasecmp(mName.c_str(), sctp_map->name.c_str()) == 0; + } + + return false; + } + + virtual void AddToMediaSection(SdpMediaSection& msection) const override { + if (mEnabled && msection.GetMediaType() == type) { + if (mDirection == sdp::kRecv) { + msection.AddDataChannel(mName, mLocalPort, mChannels, + mLocalMaxMessageSize); + } + + AddParametersToMSection(msection); + } + } + + bool Negotiate(const std::string& pt, const SdpMediaSection& remoteMsection, + bool remoteIsOffer, + Maybe<const SdpMediaSection&> localMsection) override { + JsepCodecDescription::Negotiate(pt, remoteMsection, remoteIsOffer, + localMsection); + + uint32_t message_size; + mRemoteMMSSet = remoteMsection.GetMaxMessageSize(&message_size); + if (mRemoteMMSSet) { + mRemoteMaxMessageSize = message_size; + } else { + mRemoteMaxMessageSize = + WEBRTC_DATACHANNEL_MAX_MESSAGE_SIZE_REMOTE_DEFAULT; + } + + int sctp_port = remoteMsection.GetSctpPort(); + if (sctp_port) { + mRemotePort = sctp_port; + return true; + } + + const SdpSctpmapAttributeList::Sctpmap* sctp_map( + remoteMsection.GetSctpmap()); + if (sctp_map) { + mRemotePort = std::stoi(sctp_map->pt); + return true; + } + + return false; + } + + // We only support one datachannel per m-section + void EnsureNoDuplicatePayloadTypes(std::set<std::string>& aUsedPts) override { + } + + uint16_t mLocalPort; + uint32_t mLocalMaxMessageSize; + uint16_t mRemotePort; + uint32_t mRemoteMaxMessageSize; + bool mRemoteMMSSet; +}; + +} // namespace mozilla + +#endif |