/* 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 #include #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 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 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 ApplyConfigToFmtp( UniquePtr& aFmtp) const = 0; 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& aUsedPts) { mEnabled = EnsurePayloadTypeNotDuplicate(aUsedPts, mDefaultPt); } bool EnsurePayloadTypeNotDuplicate(std::set& 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 aParams) { mSdpFmtpLine = aParams.map([](const auto& aFmtp) { std::stringstream ss; aFmtp.Serialize(ss); return ss.str(); }); } std::string mDefaultPt; std::string mName; Maybe mSdpFmtpLine; mutable Maybe 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 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("109", "opus", 48000, 2); } static UniquePtr CreateDefaultG722() { return MakeUnique("9", "G722", 8000, 1); } static UniquePtr CreateDefaultPCMU() { return MakeUnique("0", "PCMU", 8000, 1); } static UniquePtr CreateDefaultPCMA() { return MakeUnique("8", "PCMA", 8000, 1); } static UniquePtr CreateDefaultTelephoneEvent() { return MakeUnique("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(*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( *params); } return result; } void AddParametersToMSection(SdpMediaSection& msection) const override { if (mDirection == sdp::kSend) { return; } if (mName == "opus") { UniquePtr opusParams = MakeUnique( GetOpusParameters(mDefaultPt, msection)); ApplyConfigToFmtp(opusParams); 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 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; } void ApplyConfigToFmtp( UniquePtr& aFmtp) const override { if (mName == "opus") { SdpFmtpAttributeList::OpusParameters opusParams; if (aFmtp) { MOZ_RELEASE_ASSERT(aFmtp->codec_type == SdpRtpmapAttributeList::kOpus); opusParams = static_cast(*aFmtp); opusParams.useInBandFec = mFECEnabled ? 1 : 0; } else { // If we weren't passed a fmtp to use then show we can do in band FEC // for getCapabilities queries. opusParams.useInBandFec = 1; } 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.useDTX = mDTXEnabled; opusParams.frameSizeMs = mFrameSizeMs; opusParams.minFrameSizeMs = mMinFrameSizeMs; opusParams.maxFrameSizeMs = mMaxFrameSizeMs; opusParams.useCbr = mCbrEnabled; aFmtp.reset(opusParams.Clone()); } }; 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 CreateDefaultVP8(bool aUseRtx) { auto codec = MakeUnique("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 CreateDefaultVP9(bool aUseRtx) { auto codec = MakeUnique("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 CreateDefaultH264_0( bool aUseRtx) { auto codec = MakeUnique("97", "H264", 90000); codec->mPacketizationMode = 0; // Defaults for mandatory params codec->mProfileLevelId = 0x42E00D; if (aUseRtx) { codec->EnableRtx("98"); } return codec; } static UniquePtr CreateDefaultH264_1( bool aUseRtx) { auto codec = MakeUnique("126", "H264", 90000); codec->mPacketizationMode = 1; // Defaults for mandatory params codec->mProfileLevelId = 0x42E00D; if (aUseRtx) { codec->EnableRtx("127"); } return codec; } static UniquePtr CreateDefaultUlpFec() { return MakeUnique( "123", // payload type "ulpfec", // codec name 90000 // clock rate (match other video codecs) ); } static UniquePtr CreateDefaultRed() { return MakeUnique( "122", // payload type "red", // codec name 90000 // clock rate (match other video codecs) ); } void ApplyConfigToFmtp( UniquePtr& aFmtp) const override { if (mName == "H264") { SdpFmtpAttributeList::H264Parameters h264Params; if (aFmtp) { MOZ_RELEASE_ASSERT(aFmtp->codec_type == SdpRtpmapAttributeList::kH264); h264Params = static_cast(*aFmtp); } 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; // 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; aFmtp.reset(h264Params.Clone()); } else if (mName == "VP8" || mName == "VP9") { SdpRtpmapAttributeList::CodecType type = mName == "VP8" ? SdpRtpmapAttributeList::CodecType::kVP8 : SdpRtpmapAttributeList::CodecType::kVP9; auto vp8Params = SdpFmtpAttributeList::VP8Parameters(type); if (aFmtp) { MOZ_RELEASE_ASSERT(aFmtp->codec_type == type); vp8Params = static_cast(*aFmtp); } // VP8 and VP9 share the same SDP parameters thus far vp8Params.max_fs = mConstraints.maxFs; if (mConstraints.maxFps.isSome()) { vp8Params.max_fr = static_cast(std::round(*mConstraints.maxFps)); } else { vp8Params.max_fr = 60; } aFmtp.reset(vp8Params.Clone()); } } 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") { UniquePtr h264Params = MakeUnique( GetH264Parameters(mDefaultPt, msection)); ApplyConfigToFmtp(h264Params); 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 UniquePtr vp8Params = MakeUnique( GetVP8Parameters(mDefaultPt, msection)); ApplyConfigToFmtp(vp8Params); 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(*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(*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(*params); } return result; } Maybe GetRtxPtByApt(const std::string& apt, const SdpMediaSection& msection) const { Maybe 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(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(*params); } return result; } void NegotiateRtcpFb(const SdpMediaSection& remoteMsection, SdpRtcpFbAttributeList::Type type, std::vector* supportedTypes) { Maybe remoteFmt = GetMatchingFormat(remoteMsection); if (!remoteFmt) { return; } std::vector temp; for (auto& subType : *supportedTypes) { if (remoteMsection.HasRtcpFb(*remoteFmt, type, subType)) { temp.push_back(subType); } } *supportedTypes = temp; } void NegotiateRtcpFb( const SdpMediaSection& remoteMsection, std::vector* supportedFbs) { Maybe remoteFmt = GetMatchingFormat(remoteMsection); if (!remoteFmt) { return; } std::vector 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 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 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>& 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& 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 mAckFbTypes; std::vector mNackFbTypes; std::vector mCcmFbTypes; std::vector mOtherFbTypes; bool mTmmbrEnabled; bool mRembEnabled; bool mFECEnabled; bool mTransportCCEnabled; bool mRtxEnabled; std::string mREDPayloadType; std::string mULPFECPayloadType; std::string mRtxPayloadType; std::vector 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 CreateDefault() { return MakeUnique( "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 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& aUsedPts) override { } void ApplyConfigToFmtp( UniquePtr& aFmtp) const override{}; uint16_t mLocalPort; uint32_t mLocalMaxMessageSize; uint16_t mRemotePort; uint32_t mRemoteMaxMessageSize; bool mRemoteMMSSet; }; } // namespace mozilla #endif