diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /third_party/libwebrtc/pc/media_session.cc | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/pc/media_session.cc')
-rw-r--r-- | third_party/libwebrtc/pc/media_session.cc | 3163 |
1 files changed, 3163 insertions, 0 deletions
diff --git a/third_party/libwebrtc/pc/media_session.cc b/third_party/libwebrtc/pc/media_session.cc new file mode 100644 index 0000000000..e703b44101 --- /dev/null +++ b/third_party/libwebrtc/pc/media_session.cc @@ -0,0 +1,3163 @@ +/* + * Copyright 2004 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "pc/media_session.h" + +#include <stddef.h> + +#include <algorithm> +#include <map> +#include <string> +#include <unordered_map> +#include <utility> + +#include "absl/algorithm/container.h" +#include "absl/strings/match.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "api/crypto_params.h" +#include "media/base/codec.h" +#include "media/base/media_constants.h" +#include "media/base/media_engine.h" +#include "media/base/sdp_video_format_utils.h" +#include "media/sctp/sctp_transport_internal.h" +#include "p2p/base/p2p_constants.h" +#include "pc/media_protocol_names.h" +#include "pc/rtp_media_utils.h" +#include "pc/used_ids.h" +#include "rtc_base/checks.h" +#include "rtc_base/helpers.h" +#include "rtc_base/logging.h" +#include "rtc_base/ssl_stream_adapter.h" +#include "rtc_base/string_encode.h" +#include "rtc_base/third_party/base64/base64.h" +#include "rtc_base/unique_id_generator.h" + +namespace { + +using rtc::UniqueRandomIdGenerator; +using webrtc::RtpTransceiverDirection; + +const char kInline[] = "inline:"; + +void GetSupportedSdesCryptoSuiteNames( + void (*func)(const webrtc::CryptoOptions&, std::vector<int>*), + const webrtc::CryptoOptions& crypto_options, + std::vector<std::string>* names) { + std::vector<int> crypto_suites; + func(crypto_options, &crypto_suites); + for (const auto crypto : crypto_suites) { + names->push_back(rtc::SrtpCryptoSuiteToName(crypto)); + } +} + +webrtc::RtpExtension RtpExtensionFromCapability( + const webrtc::RtpHeaderExtensionCapability& capability) { + return webrtc::RtpExtension(capability.uri, + capability.preferred_id.value_or(1)); +} + +cricket::RtpHeaderExtensions RtpHeaderExtensionsFromCapabilities( + const std::vector<webrtc::RtpHeaderExtensionCapability>& capabilities) { + cricket::RtpHeaderExtensions exts; + for (const auto& capability : capabilities) { + exts.push_back(RtpExtensionFromCapability(capability)); + } + return exts; +} + +std::vector<webrtc::RtpHeaderExtensionCapability> +UnstoppedRtpHeaderExtensionCapabilities( + std::vector<webrtc::RtpHeaderExtensionCapability> capabilities) { + capabilities.erase( + std::remove_if( + capabilities.begin(), capabilities.end(), + [](const webrtc::RtpHeaderExtensionCapability& capability) { + return capability.direction == RtpTransceiverDirection::kStopped; + }), + capabilities.end()); + return capabilities; +} + +bool IsCapabilityPresent(const webrtc::RtpHeaderExtensionCapability& capability, + const cricket::RtpHeaderExtensions& extensions) { + return std::find_if(extensions.begin(), extensions.end(), + [&capability](const webrtc::RtpExtension& extension) { + return capability.uri == extension.uri; + }) != extensions.end(); +} + +cricket::RtpHeaderExtensions UnstoppedOrPresentRtpHeaderExtensions( + const std::vector<webrtc::RtpHeaderExtensionCapability>& capabilities, + const cricket::RtpHeaderExtensions& unencrypted, + const cricket::RtpHeaderExtensions& encrypted) { + cricket::RtpHeaderExtensions extensions; + for (const auto& capability : capabilities) { + if (capability.direction != RtpTransceiverDirection::kStopped || + IsCapabilityPresent(capability, unencrypted) || + IsCapabilityPresent(capability, encrypted)) { + extensions.push_back(RtpExtensionFromCapability(capability)); + } + } + return extensions; +} + +} // namespace + +namespace cricket { + +static RtpTransceiverDirection NegotiateRtpTransceiverDirection( + RtpTransceiverDirection offer, + RtpTransceiverDirection wants) { + bool offer_send = webrtc::RtpTransceiverDirectionHasSend(offer); + bool offer_recv = webrtc::RtpTransceiverDirectionHasRecv(offer); + bool wants_send = webrtc::RtpTransceiverDirectionHasSend(wants); + bool wants_recv = webrtc::RtpTransceiverDirectionHasRecv(wants); + return webrtc::RtpTransceiverDirectionFromSendRecv(offer_recv && wants_send, + offer_send && wants_recv); +} + +static bool IsMediaContentOfType(const ContentInfo* content, + MediaType media_type) { + if (!content || !content->media_description()) { + return false; + } + return content->media_description()->type() == media_type; +} + +static bool CreateCryptoParams(int tag, + const std::string& cipher, + CryptoParams* crypto_out) { + int key_len; + int salt_len; + if (!rtc::GetSrtpKeyAndSaltLengths(rtc::SrtpCryptoSuiteFromName(cipher), + &key_len, &salt_len)) { + return false; + } + + int master_key_len = key_len + salt_len; + std::string master_key; + if (!rtc::CreateRandomData(master_key_len, &master_key)) { + return false; + } + + RTC_CHECK_EQ(master_key_len, master_key.size()); + std::string key = rtc::Base64::Encode(master_key); + + crypto_out->tag = tag; + crypto_out->cipher_suite = cipher; + crypto_out->key_params = kInline; + crypto_out->key_params += key; + return true; +} + +static bool AddCryptoParams(const std::string& cipher_suite, + CryptoParamsVec* cryptos_out) { + int size = static_cast<int>(cryptos_out->size()); + + cryptos_out->resize(size + 1); + return CreateCryptoParams(size, cipher_suite, &cryptos_out->at(size)); +} + +void AddMediaCryptos(const CryptoParamsVec& cryptos, + MediaContentDescription* media) { + for (const CryptoParams& crypto : cryptos) { + media->AddCrypto(crypto); + } +} + +bool CreateMediaCryptos(const std::vector<std::string>& crypto_suites, + MediaContentDescription* media) { + CryptoParamsVec cryptos; + for (const std::string& crypto_suite : crypto_suites) { + if (!AddCryptoParams(crypto_suite, &cryptos)) { + return false; + } + } + AddMediaCryptos(cryptos, media); + return true; +} + +const CryptoParamsVec* GetCryptos(const ContentInfo* content) { + if (!content || !content->media_description()) { + return nullptr; + } + return &content->media_description()->cryptos(); +} + +bool FindMatchingCrypto(const CryptoParamsVec& cryptos, + const CryptoParams& crypto, + CryptoParams* crypto_out) { + auto it = absl::c_find_if( + cryptos, [&crypto](const CryptoParams& c) { return crypto.Matches(c); }); + if (it == cryptos.end()) { + return false; + } + *crypto_out = *it; + return true; +} + +// For audio, HMAC 32 (if enabled) is prefered over HMAC 80 because of the +// low overhead. +void GetSupportedAudioSdesCryptoSuites( + const webrtc::CryptoOptions& crypto_options, + std::vector<int>* crypto_suites) { + if (crypto_options.srtp.enable_aes128_sha1_32_crypto_cipher) { + crypto_suites->push_back(rtc::kSrtpAes128CmSha1_32); + } + crypto_suites->push_back(rtc::kSrtpAes128CmSha1_80); + if (crypto_options.srtp.enable_gcm_crypto_suites) { + crypto_suites->push_back(rtc::kSrtpAeadAes256Gcm); + crypto_suites->push_back(rtc::kSrtpAeadAes128Gcm); + } +} + +void GetSupportedAudioSdesCryptoSuiteNames( + const webrtc::CryptoOptions& crypto_options, + std::vector<std::string>* crypto_suite_names) { + GetSupportedSdesCryptoSuiteNames(GetSupportedAudioSdesCryptoSuites, + crypto_options, crypto_suite_names); +} + +void GetSupportedVideoSdesCryptoSuites( + const webrtc::CryptoOptions& crypto_options, + std::vector<int>* crypto_suites) { + crypto_suites->push_back(rtc::kSrtpAes128CmSha1_80); + if (crypto_options.srtp.enable_gcm_crypto_suites) { + crypto_suites->push_back(rtc::kSrtpAeadAes256Gcm); + crypto_suites->push_back(rtc::kSrtpAeadAes128Gcm); + } +} + +void GetSupportedVideoSdesCryptoSuiteNames( + const webrtc::CryptoOptions& crypto_options, + std::vector<std::string>* crypto_suite_names) { + GetSupportedSdesCryptoSuiteNames(GetSupportedVideoSdesCryptoSuites, + crypto_options, crypto_suite_names); +} + +void GetSupportedDataSdesCryptoSuites( + const webrtc::CryptoOptions& crypto_options, + std::vector<int>* crypto_suites) { + crypto_suites->push_back(rtc::kSrtpAes128CmSha1_80); + if (crypto_options.srtp.enable_gcm_crypto_suites) { + crypto_suites->push_back(rtc::kSrtpAeadAes256Gcm); + crypto_suites->push_back(rtc::kSrtpAeadAes128Gcm); + } +} + +void GetSupportedDataSdesCryptoSuiteNames( + const webrtc::CryptoOptions& crypto_options, + std::vector<std::string>* crypto_suite_names) { + GetSupportedSdesCryptoSuiteNames(GetSupportedDataSdesCryptoSuites, + crypto_options, crypto_suite_names); +} + +// Support any GCM cipher (if enabled through options). For video support only +// 80-bit SHA1 HMAC. For audio 32-bit HMAC is tolerated (if enabled) unless +// bundle is enabled because it is low overhead. +// Pick the crypto in the list that is supported. +static bool SelectCrypto(const MediaContentDescription* offer, + bool bundle, + const webrtc::CryptoOptions& crypto_options, + CryptoParams* crypto_out) { + bool audio = offer->type() == MEDIA_TYPE_AUDIO; + const CryptoParamsVec& cryptos = offer->cryptos(); + + for (const CryptoParams& crypto : cryptos) { + if ((crypto_options.srtp.enable_gcm_crypto_suites && + rtc::IsGcmCryptoSuiteName(crypto.cipher_suite)) || + rtc::kCsAesCm128HmacSha1_80 == crypto.cipher_suite || + (rtc::kCsAesCm128HmacSha1_32 == crypto.cipher_suite && audio && + !bundle && crypto_options.srtp.enable_aes128_sha1_32_crypto_cipher)) { + return CreateCryptoParams(crypto.tag, crypto.cipher_suite, crypto_out); + } + } + return false; +} + +// Finds all StreamParams of all media types and attach them to stream_params. +static StreamParamsVec GetCurrentStreamParams( + const std::vector<const ContentInfo*>& active_local_contents) { + StreamParamsVec stream_params; + for (const ContentInfo* content : active_local_contents) { + for (const StreamParams& params : content->media_description()->streams()) { + stream_params.push_back(params); + } + } + return stream_params; +} + +static StreamParams CreateStreamParamsForNewSenderWithSsrcs( + const SenderOptions& sender, + const std::string& rtcp_cname, + bool include_rtx_streams, + bool include_flexfec_stream, + UniqueRandomIdGenerator* ssrc_generator, + const webrtc::FieldTrialsView& field_trials) { + StreamParams result; + result.id = sender.track_id; + + // TODO(brandtr): Update when we support multistream protection. + if (include_flexfec_stream && sender.num_sim_layers > 1) { + include_flexfec_stream = false; + RTC_LOG(LS_WARNING) + << "Our FlexFEC implementation only supports protecting " + "a single media streams. This session has multiple " + "media streams however, so no FlexFEC SSRC will be generated."; + } + if (include_flexfec_stream && !field_trials.IsEnabled("WebRTC-FlexFEC-03")) { + include_flexfec_stream = false; + RTC_LOG(LS_WARNING) + << "WebRTC-FlexFEC trial is not enabled, not sending FlexFEC"; + } + + result.GenerateSsrcs(sender.num_sim_layers, include_rtx_streams, + include_flexfec_stream, ssrc_generator); + + result.cname = rtcp_cname; + result.set_stream_ids(sender.stream_ids); + + return result; +} + +static bool ValidateSimulcastLayers( + const std::vector<RidDescription>& rids, + const SimulcastLayerList& simulcast_layers) { + return absl::c_all_of( + simulcast_layers.GetAllLayers(), [&rids](const SimulcastLayer& layer) { + return absl::c_any_of(rids, [&layer](const RidDescription& rid) { + return rid.rid == layer.rid; + }); + }); +} + +static StreamParams CreateStreamParamsForNewSenderWithRids( + const SenderOptions& sender, + const std::string& rtcp_cname) { + RTC_DCHECK(!sender.rids.empty()); + RTC_DCHECK_EQ(sender.num_sim_layers, 0) + << "RIDs are the compliant way to indicate simulcast."; + RTC_DCHECK(ValidateSimulcastLayers(sender.rids, sender.simulcast_layers)); + StreamParams result; + result.id = sender.track_id; + result.cname = rtcp_cname; + result.set_stream_ids(sender.stream_ids); + + // More than one rid should be signaled. + if (sender.rids.size() > 1) { + result.set_rids(sender.rids); + } + + return result; +} + +// Adds SimulcastDescription if indicated by the media description options. +// MediaContentDescription should already be set up with the send rids. +static void AddSimulcastToMediaDescription( + const MediaDescriptionOptions& media_description_options, + MediaContentDescription* description) { + RTC_DCHECK(description); + + // Check if we are using RIDs in this scenario. + if (absl::c_all_of(description->streams(), [](const StreamParams& params) { + return !params.has_rids(); + })) { + return; + } + + RTC_DCHECK_EQ(1, description->streams().size()) + << "RIDs are only supported in Unified Plan semantics."; + RTC_DCHECK_EQ(1, media_description_options.sender_options.size()); + RTC_DCHECK(description->type() == MediaType::MEDIA_TYPE_AUDIO || + description->type() == MediaType::MEDIA_TYPE_VIDEO); + + // One RID or less indicates that simulcast is not needed. + if (description->streams()[0].rids().size() <= 1) { + return; + } + + // Only negotiate the send layers. + SimulcastDescription simulcast; + simulcast.send_layers() = + media_description_options.sender_options[0].simulcast_layers; + description->set_simulcast_description(simulcast); +} + +// Adds a StreamParams for each SenderOptions in `sender_options` to +// content_description. +// `current_params` - All currently known StreamParams of any media type. +template <class C> +static bool AddStreamParams(const std::vector<SenderOptions>& sender_options, + const std::string& rtcp_cname, + UniqueRandomIdGenerator* ssrc_generator, + StreamParamsVec* current_streams, + MediaContentDescriptionImpl<C>* content_description, + const webrtc::FieldTrialsView& field_trials) { + // SCTP streams are not negotiated using SDP/ContentDescriptions. + if (IsSctpProtocol(content_description->protocol())) { + return true; + } + + const bool include_rtx_streams = + ContainsRtxCodec(content_description->codecs()); + + const bool include_flexfec_stream = + ContainsFlexfecCodec(content_description->codecs()); + + for (const SenderOptions& sender : sender_options) { + StreamParams* param = GetStreamByIds(*current_streams, sender.track_id); + if (!param) { + // This is a new sender. + StreamParams stream_param = + sender.rids.empty() + ? + // Signal SSRCs and legacy simulcast (if requested). + CreateStreamParamsForNewSenderWithSsrcs( + sender, rtcp_cname, include_rtx_streams, + include_flexfec_stream, ssrc_generator, field_trials) + : + // Signal RIDs and spec-compliant simulcast (if requested). + CreateStreamParamsForNewSenderWithRids(sender, rtcp_cname); + + content_description->AddStream(stream_param); + + // Store the new StreamParams in current_streams. + // This is necessary so that we can use the CNAME for other media types. + current_streams->push_back(stream_param); + } else { + // Use existing generated SSRCs/groups, but update the sync_label if + // necessary. This may be needed if a MediaStreamTrack was moved from one + // MediaStream to another. + param->set_stream_ids(sender.stream_ids); + content_description->AddStream(*param); + } + } + return true; +} + +// Updates the transport infos of the `sdesc` according to the given +// `bundle_group`. The transport infos of the content names within the +// `bundle_group` should be updated to use the ufrag, pwd and DTLS role of the +// first content within the `bundle_group`. +static bool UpdateTransportInfoForBundle(const ContentGroup& bundle_group, + SessionDescription* sdesc) { + // The bundle should not be empty. + if (!sdesc || !bundle_group.FirstContentName()) { + return false; + } + + // We should definitely have a transport for the first content. + const std::string& selected_content_name = *bundle_group.FirstContentName(); + const TransportInfo* selected_transport_info = + sdesc->GetTransportInfoByName(selected_content_name); + if (!selected_transport_info) { + return false; + } + + // Set the other contents to use the same ICE credentials. + const std::string& selected_ufrag = + selected_transport_info->description.ice_ufrag; + const std::string& selected_pwd = + selected_transport_info->description.ice_pwd; + ConnectionRole selected_connection_role = + selected_transport_info->description.connection_role; + for (TransportInfo& transport_info : sdesc->transport_infos()) { + if (bundle_group.HasContentName(transport_info.content_name) && + transport_info.content_name != selected_content_name) { + transport_info.description.ice_ufrag = selected_ufrag; + transport_info.description.ice_pwd = selected_pwd; + transport_info.description.connection_role = selected_connection_role; + } + } + return true; +} + +// Gets the CryptoParamsVec of the given `content_name` from `sdesc`, and +// sets it to `cryptos`. +static bool GetCryptosByName(const SessionDescription* sdesc, + const std::string& content_name, + CryptoParamsVec* cryptos) { + if (!sdesc || !cryptos) { + return false; + } + const ContentInfo* content = sdesc->GetContentByName(content_name); + if (!content || !content->media_description()) { + return false; + } + *cryptos = content->media_description()->cryptos(); + return true; +} + +// Prunes the `target_cryptos` by removing the crypto params (cipher_suite) +// which are not available in `filter`. +static void PruneCryptos(const CryptoParamsVec& filter, + CryptoParamsVec* target_cryptos) { + if (!target_cryptos) { + return; + } + + target_cryptos->erase( + std::remove_if(target_cryptos->begin(), target_cryptos->end(), + // Returns true if the `crypto`'s cipher_suite is not + // found in `filter`. + [&filter](const CryptoParams& crypto) { + for (const CryptoParams& entry : filter) { + if (entry.cipher_suite == crypto.cipher_suite) + return false; + } + return true; + }), + target_cryptos->end()); +} + +static bool IsRtpContent(SessionDescription* sdesc, + const std::string& content_name) { + bool is_rtp = false; + ContentInfo* content = sdesc->GetContentByName(content_name); + if (content && content->media_description()) { + is_rtp = IsRtpProtocol(content->media_description()->protocol()); + } + return is_rtp; +} + +// Updates the crypto parameters of the `sdesc` according to the given +// `bundle_group`. The crypto parameters of all the contents within the +// `bundle_group` should be updated to use the common subset of the +// available cryptos. +static bool UpdateCryptoParamsForBundle(const ContentGroup& bundle_group, + SessionDescription* sdesc) { + // The bundle should not be empty. + if (!sdesc || !bundle_group.FirstContentName()) { + return false; + } + + bool common_cryptos_needed = false; + // Get the common cryptos. + const ContentNames& content_names = bundle_group.content_names(); + CryptoParamsVec common_cryptos; + bool first = true; + for (const std::string& content_name : content_names) { + if (!IsRtpContent(sdesc, content_name)) { + continue; + } + // The common cryptos are needed if any of the content does not have DTLS + // enabled. + if (!sdesc->GetTransportInfoByName(content_name)->description.secure()) { + common_cryptos_needed = true; + } + if (first) { + first = false; + // Initial the common_cryptos with the first content in the bundle group. + if (!GetCryptosByName(sdesc, content_name, &common_cryptos)) { + return false; + } + if (common_cryptos.empty()) { + // If there's no crypto params, we should just return. + return true; + } + } else { + CryptoParamsVec cryptos; + if (!GetCryptosByName(sdesc, content_name, &cryptos)) { + return false; + } + PruneCryptos(cryptos, &common_cryptos); + } + } + + if (common_cryptos.empty() && common_cryptos_needed) { + return false; + } + + // Update to use the common cryptos. + for (const std::string& content_name : content_names) { + if (!IsRtpContent(sdesc, content_name)) { + continue; + } + ContentInfo* content = sdesc->GetContentByName(content_name); + if (IsMediaContent(content)) { + MediaContentDescription* media_desc = content->media_description(); + if (!media_desc) { + return false; + } + media_desc->set_cryptos(common_cryptos); + } + } + return true; +} + +static std::vector<const ContentInfo*> GetActiveContents( + const SessionDescription& description, + const MediaSessionOptions& session_options) { + std::vector<const ContentInfo*> active_contents; + for (size_t i = 0; i < description.contents().size(); ++i) { + RTC_DCHECK_LT(i, session_options.media_description_options.size()); + const ContentInfo& content = description.contents()[i]; + const MediaDescriptionOptions& media_options = + session_options.media_description_options[i]; + if (!content.rejected && !media_options.stopped && + content.name == media_options.mid) { + active_contents.push_back(&content); + } + } + return active_contents; +} + +template <class C> +static bool ContainsRtxCodec(const std::vector<C>& codecs) { + for (const auto& codec : codecs) { + if (IsRtxCodec(codec)) { + return true; + } + } + return false; +} + +template <class C> +static bool IsRedCodec(const C& codec) { + return absl::EqualsIgnoreCase(codec.name, kRedCodecName); +} + +template <class C> +static bool IsRtxCodec(const C& codec) { + return absl::EqualsIgnoreCase(codec.name, kRtxCodecName); +} + +template <class C> +static bool ContainsFlexfecCodec(const std::vector<C>& codecs) { + for (const auto& codec : codecs) { + if (IsFlexfecCodec(codec)) { + return true; + } + } + return false; +} + +template <class C> +static bool IsFlexfecCodec(const C& codec) { + return absl::EqualsIgnoreCase(codec.name, kFlexfecCodecName); +} + +template <class C> +static bool IsUlpfecCodec(const C& codec) { + return absl::EqualsIgnoreCase(codec.name, kUlpfecCodecName); +} + +template <class C> +static bool IsComfortNoiseCodec(const C& codec) { + return absl::EqualsIgnoreCase(codec.name, kComfortNoiseCodecName); +} + +// Create a media content to be offered for the given `sender_options`, +// according to the given options.rtcp_mux, session_options.is_muc, codecs, +// secure_transport, crypto, and current_streams. If we don't currently have +// crypto (in current_cryptos) and it is enabled (in secure_policy), crypto is +// created (according to crypto_suites). The created content is added to the +// offer. +static bool CreateContentOffer( + const MediaDescriptionOptions& media_description_options, + const MediaSessionOptions& session_options, + const SecurePolicy& secure_policy, + const CryptoParamsVec* current_cryptos, + const std::vector<std::string>& crypto_suites, + const RtpHeaderExtensions& rtp_extensions, + UniqueRandomIdGenerator* ssrc_generator, + StreamParamsVec* current_streams, + MediaContentDescription* offer) { + offer->set_rtcp_mux(session_options.rtcp_mux_enabled); + if (offer->type() == cricket::MEDIA_TYPE_VIDEO) { + offer->set_rtcp_reduced_size(true); + } + + // Build the vector of header extensions with directions for this + // media_description's options. + RtpHeaderExtensions extensions; + for (auto extension_with_id : rtp_extensions) { + for (const auto& extension : media_description_options.header_extensions) { + if (extension_with_id.uri == extension.uri) { + // TODO(crbug.com/1051821): Configure the extension direction from + // the information in the media_description_options extension + // capability. + extensions.push_back(extension_with_id); + } + } + } + offer->set_rtp_header_extensions(extensions); + + AddSimulcastToMediaDescription(media_description_options, offer); + + if (secure_policy != SEC_DISABLED) { + if (current_cryptos) { + AddMediaCryptos(*current_cryptos, offer); + } + if (offer->cryptos().empty()) { + if (!CreateMediaCryptos(crypto_suites, offer)) { + return false; + } + } + } + + if (secure_policy == SEC_REQUIRED && offer->cryptos().empty()) { + return false; + } + return true; +} +template <class C> +static bool CreateMediaContentOffer( + const MediaDescriptionOptions& media_description_options, + const MediaSessionOptions& session_options, + const std::vector<C>& codecs, + const SecurePolicy& secure_policy, + const CryptoParamsVec* current_cryptos, + const std::vector<std::string>& crypto_suites, + const RtpHeaderExtensions& rtp_extensions, + UniqueRandomIdGenerator* ssrc_generator, + StreamParamsVec* current_streams, + MediaContentDescriptionImpl<C>* offer, + const webrtc::FieldTrialsView& field_trials) { + offer->AddCodecs(codecs); + if (!AddStreamParams(media_description_options.sender_options, + session_options.rtcp_cname, ssrc_generator, + current_streams, offer, field_trials)) { + return false; + } + + return CreateContentOffer(media_description_options, session_options, + secure_policy, current_cryptos, crypto_suites, + rtp_extensions, ssrc_generator, current_streams, + offer); +} + +template <class C> +static bool ReferencedCodecsMatch(const std::vector<C>& codecs1, + const int codec1_id, + const std::vector<C>& codecs2, + const int codec2_id, + const webrtc::FieldTrialsView* field_trials) { + const C* codec1 = FindCodecById(codecs1, codec1_id); + const C* codec2 = FindCodecById(codecs2, codec2_id); + return codec1 != nullptr && codec2 != nullptr && + codec1->Matches(*codec2, field_trials); +} + +template <class C> +static void NegotiatePacketization(const C& local_codec, + const C& remote_codec, + C* negotiated_codec) {} + +template <> +void NegotiatePacketization(const VideoCodec& local_codec, + const VideoCodec& remote_codec, + VideoCodec* negotiated_codec) { + negotiated_codec->packetization = + VideoCodec::IntersectPacketization(local_codec, remote_codec); +} + +template <class C> +static void NegotiateCodecs(const std::vector<C>& local_codecs, + const std::vector<C>& offered_codecs, + std::vector<C>* negotiated_codecs, + bool keep_offer_order, + const webrtc::FieldTrialsView* field_trials) { + for (const C& ours : local_codecs) { + C theirs; + // Note that we intentionally only find one matching codec for each of our + // local codecs, in case the remote offer contains duplicate codecs. + if (FindMatchingCodec(local_codecs, offered_codecs, ours, &theirs, + field_trials)) { + C negotiated = ours; + NegotiatePacketization(ours, theirs, &negotiated); + negotiated.IntersectFeedbackParams(theirs); + if (IsRtxCodec(negotiated)) { + const auto apt_it = + theirs.params.find(kCodecParamAssociatedPayloadType); + // FindMatchingCodec shouldn't return something with no apt value. + RTC_DCHECK(apt_it != theirs.params.end()); + negotiated.SetParam(kCodecParamAssociatedPayloadType, apt_it->second); + + // We support parsing the declarative rtx-time parameter. + const auto rtx_time_it = theirs.params.find(kCodecParamRtxTime); + if (rtx_time_it != theirs.params.end()) { + negotiated.SetParam(kCodecParamRtxTime, rtx_time_it->second); + } + } else if (IsRedCodec(negotiated)) { + const auto red_it = theirs.params.find(kCodecParamNotInNameValueFormat); + if (red_it != theirs.params.end()) { + negotiated.SetParam(kCodecParamNotInNameValueFormat, red_it->second); + } + } + if (absl::EqualsIgnoreCase(ours.name, kH264CodecName)) { + webrtc::H264GenerateProfileLevelIdForAnswer(ours.params, theirs.params, + &negotiated.params); + } + negotiated.id = theirs.id; + negotiated.name = theirs.name; + negotiated_codecs->push_back(std::move(negotiated)); + } + } + if (keep_offer_order) { + // RFC3264: Although the answerer MAY list the formats in their desired + // order of preference, it is RECOMMENDED that unless there is a + // specific reason, the answerer list formats in the same relative order + // they were present in the offer. + // This can be skipped when the transceiver has any codec preferences. + std::unordered_map<int, int> payload_type_preferences; + int preference = static_cast<int>(offered_codecs.size() + 1); + for (const C& codec : offered_codecs) { + payload_type_preferences[codec.id] = preference--; + } + absl::c_sort(*negotiated_codecs, [&payload_type_preferences](const C& a, + const C& b) { + return payload_type_preferences[a.id] > payload_type_preferences[b.id]; + }); + } +} + +// Finds a codec in `codecs2` that matches `codec_to_match`, which is +// a member of `codecs1`. If `codec_to_match` is an RED or RTX codec, both +// the codecs themselves and their associated codecs must match. +template <class C> +static bool FindMatchingCodec(const std::vector<C>& codecs1, + const std::vector<C>& codecs2, + const C& codec_to_match, + C* found_codec, + const webrtc::FieldTrialsView* field_trials) { + // `codec_to_match` should be a member of `codecs1`, in order to look up + // RED/RTX codecs' associated codecs correctly. If not, that's a programming + // error. + RTC_DCHECK(absl::c_any_of(codecs1, [&codec_to_match](const C& codec) { + return &codec == &codec_to_match; + })); + for (const C& potential_match : codecs2) { + if (potential_match.Matches(codec_to_match, field_trials)) { + if (IsRtxCodec(codec_to_match)) { + int apt_value_1 = 0; + int apt_value_2 = 0; + if (!codec_to_match.GetParam(kCodecParamAssociatedPayloadType, + &apt_value_1) || + !potential_match.GetParam(kCodecParamAssociatedPayloadType, + &apt_value_2)) { + RTC_LOG(LS_WARNING) << "RTX missing associated payload type."; + continue; + } + if (!ReferencedCodecsMatch(codecs1, apt_value_1, codecs2, apt_value_2, + field_trials)) { + continue; + } + } else if (IsRedCodec(codec_to_match)) { + auto red_parameters_1 = + codec_to_match.params.find(kCodecParamNotInNameValueFormat); + auto red_parameters_2 = + potential_match.params.find(kCodecParamNotInNameValueFormat); + bool has_parameters_1 = red_parameters_1 != codec_to_match.params.end(); + bool has_parameters_2 = + red_parameters_2 != potential_match.params.end(); + if (has_parameters_1 && has_parameters_2) { + // Mixed reference codecs (i.e. 111/112) are not supported. + // Different levels of redundancy between offer and answer are + // since RED is considered to be declarative. + std::vector<absl::string_view> redundant_payloads_1 = + rtc::split(red_parameters_1->second, '/'); + std::vector<absl::string_view> redundant_payloads_2 = + rtc::split(red_parameters_2->second, '/'); + if (redundant_payloads_1.size() > 0 && + redundant_payloads_2.size() > 0) { + bool consistent = true; + for (size_t i = 1; i < redundant_payloads_1.size(); i++) { + if (redundant_payloads_1[i] != redundant_payloads_1[0]) { + consistent = false; + break; + } + } + for (size_t i = 1; i < redundant_payloads_2.size(); i++) { + if (redundant_payloads_2[i] != redundant_payloads_2[0]) { + consistent = false; + break; + } + } + if (!consistent) { + continue; + } + + int red_value_1; + int red_value_2; + if (rtc::FromString(redundant_payloads_1[0], &red_value_1) && + rtc::FromString(redundant_payloads_2[0], &red_value_2)) { + if (!ReferencedCodecsMatch(codecs1, red_value_1, codecs2, + red_value_2, field_trials)) { + continue; + } + } + } + } else if (has_parameters_1 != has_parameters_2) { + continue; + } + } + if (found_codec) { + *found_codec = potential_match; + } + return true; + } + } + return false; +} + +// Find the codec in `codec_list` that `rtx_codec` is associated with. +template <class C> +static const C* GetAssociatedCodecForRtx(const std::vector<C>& codec_list, + const C& rtx_codec) { + std::string associated_pt_str; + if (!rtx_codec.GetParam(kCodecParamAssociatedPayloadType, + &associated_pt_str)) { + RTC_LOG(LS_WARNING) << "RTX codec " << rtx_codec.name + << " is missing an associated payload type."; + return nullptr; + } + + int associated_pt; + if (!rtc::FromString(associated_pt_str, &associated_pt)) { + RTC_LOG(LS_WARNING) << "Couldn't convert payload type " << associated_pt_str + << " of RTX codec " << rtx_codec.name + << " to an integer."; + return nullptr; + } + + // Find the associated codec for the RTX codec. + const C* associated_codec = FindCodecById(codec_list, associated_pt); + if (!associated_codec) { + RTC_LOG(LS_WARNING) << "Couldn't find associated codec with payload type " + << associated_pt << " for RTX codec " << rtx_codec.name + << "."; + } + return associated_codec; +} + +// Find the codec in `codec_list` that `red_codec` is associated with. +template <class C> +static const C* GetAssociatedCodecForRed(const std::vector<C>& codec_list, + const C& red_codec) { + std::string fmtp; + if (!red_codec.GetParam(kCodecParamNotInNameValueFormat, &fmtp)) { + // Normal for video/RED. + RTC_LOG(LS_WARNING) << "RED codec " << red_codec.name + << " is missing an associated payload type."; + return nullptr; + } + + std::vector<absl::string_view> redundant_payloads = rtc::split(fmtp, '/'); + if (redundant_payloads.size() < 2) { + return nullptr; + } + + absl::string_view associated_pt_str = redundant_payloads[0]; + int associated_pt; + if (!rtc::FromString(associated_pt_str, &associated_pt)) { + RTC_LOG(LS_WARNING) << "Couldn't convert first payload type " + << associated_pt_str << " of RED codec " + << red_codec.name << " to an integer."; + return nullptr; + } + + // Find the associated codec for the RED codec. + const C* associated_codec = FindCodecById(codec_list, associated_pt); + if (!associated_codec) { + RTC_LOG(LS_WARNING) << "Couldn't find associated codec with payload type " + << associated_pt << " for RED codec " << red_codec.name + << "."; + } + return associated_codec; +} + +// Adds all codecs from `reference_codecs` to `offered_codecs` that don't +// already exist in `offered_codecs` and ensure the payload types don't +// collide. +template <class C> +static void MergeCodecs(const std::vector<C>& reference_codecs, + std::vector<C>* offered_codecs, + UsedPayloadTypes* used_pltypes, + const webrtc::FieldTrialsView* field_trials) { + // Add all new codecs that are not RTX/RED codecs. + // The two-pass splitting of the loops means preferring payload types + // of actual codecs with respect to collisions. + for (const C& reference_codec : reference_codecs) { + if (!IsRtxCodec(reference_codec) && !IsRedCodec(reference_codec) && + !FindMatchingCodec<C>(reference_codecs, *offered_codecs, + reference_codec, nullptr, field_trials)) { + C codec = reference_codec; + used_pltypes->FindAndSetIdUsed(&codec); + offered_codecs->push_back(codec); + } + } + + // Add all new RTX or RED codecs. + for (const C& reference_codec : reference_codecs) { + if (IsRtxCodec(reference_codec) && + !FindMatchingCodec<C>(reference_codecs, *offered_codecs, + reference_codec, nullptr, field_trials)) { + C rtx_codec = reference_codec; + const C* associated_codec = + GetAssociatedCodecForRtx(reference_codecs, rtx_codec); + if (!associated_codec) { + continue; + } + // Find a codec in the offered list that matches the reference codec. + // Its payload type may be different than the reference codec. + C matching_codec; + if (!FindMatchingCodec<C>(reference_codecs, *offered_codecs, + *associated_codec, &matching_codec, + field_trials)) { + RTC_LOG(LS_WARNING) + << "Couldn't find matching " << associated_codec->name << " codec."; + continue; + } + + rtx_codec.params[kCodecParamAssociatedPayloadType] = + rtc::ToString(matching_codec.id); + used_pltypes->FindAndSetIdUsed(&rtx_codec); + offered_codecs->push_back(rtx_codec); + } else if (IsRedCodec(reference_codec) && + !FindMatchingCodec<C>(reference_codecs, *offered_codecs, + reference_codec, nullptr, field_trials)) { + C red_codec = reference_codec; + const C* associated_codec = + GetAssociatedCodecForRed(reference_codecs, red_codec); + if (associated_codec) { + C matching_codec; + if (!FindMatchingCodec<C>(reference_codecs, *offered_codecs, + *associated_codec, &matching_codec, + field_trials)) { + RTC_LOG(LS_WARNING) << "Couldn't find matching " + << associated_codec->name << " codec."; + continue; + } + + red_codec.params[kCodecParamNotInNameValueFormat] = + rtc::ToString(matching_codec.id) + "/" + + rtc::ToString(matching_codec.id); + } + used_pltypes->FindAndSetIdUsed(&red_codec); + offered_codecs->push_back(red_codec); + } + } +} + +// `codecs` is a full list of codecs with correct payload type mappings, which +// don't conflict with mappings of the other media type; `supported_codecs` is +// a list filtered for the media section`s direction but with default payload +// types. +template <typename Codecs> +static Codecs MatchCodecPreference( + const std::vector<webrtc::RtpCodecCapability>& codec_preferences, + const Codecs& codecs, + const Codecs& supported_codecs, + const webrtc::FieldTrialsView* field_trials) { + Codecs filtered_codecs; + bool want_rtx = false; + bool want_red = false; + + for (const auto& codec_preference : codec_preferences) { + if (IsRtxCodec(codec_preference)) { + want_rtx = true; + } else if (IsRedCodec(codec_preference)) { + want_red = true; + } + } + for (const auto& codec_preference : codec_preferences) { + auto found_codec = absl::c_find_if( + supported_codecs, + [&codec_preference](const typename Codecs::value_type& codec) { + webrtc::RtpCodecParameters codec_parameters = + codec.ToCodecParameters(); + return codec_parameters.name == codec_preference.name && + codec_parameters.kind == codec_preference.kind && + codec_parameters.num_channels == + codec_preference.num_channels && + codec_parameters.clock_rate == codec_preference.clock_rate && + codec_parameters.parameters == codec_preference.parameters; + }); + + if (found_codec != supported_codecs.end()) { + typename Codecs::value_type found_codec_with_correct_pt; + if (FindMatchingCodec(supported_codecs, codecs, *found_codec, + &found_codec_with_correct_pt, field_trials)) { + filtered_codecs.push_back(found_codec_with_correct_pt); + std::string id = rtc::ToString(found_codec_with_correct_pt.id); + // Search for the matching rtx or red codec. + if (want_red || want_rtx) { + for (const auto& codec : codecs) { + if (IsRtxCodec(codec)) { + const auto apt = + codec.params.find(cricket::kCodecParamAssociatedPayloadType); + if (apt != codec.params.end() && apt->second == id) { + filtered_codecs.push_back(codec); + break; + } + } else if (IsRedCodec(codec)) { + // For RED, do not insert the codec again if it was already + // inserted. audio/red for opus gets enabled by having RED before + // the primary codec. + const auto fmtp = + codec.params.find(cricket::kCodecParamNotInNameValueFormat); + if (fmtp != codec.params.end()) { + std::vector<absl::string_view> redundant_payloads = + rtc::split(fmtp->second, '/'); + if (redundant_payloads.size() > 0 && + redundant_payloads[0] == id) { + if (std::find(filtered_codecs.begin(), filtered_codecs.end(), + codec) == filtered_codecs.end()) { + filtered_codecs.push_back(codec); + } + break; + } + } + } + } + } + } + } + } + + return filtered_codecs; +} + +// Compute the union of `codecs1` and `codecs2`. +template <class C> +std::vector<C> ComputeCodecsUnion(const std::vector<C>& codecs1, + const std::vector<C>& codecs2, + const webrtc::FieldTrialsView* field_trials) { + std::vector<C> all_codecs; + UsedPayloadTypes used_payload_types; + for (const C& codec : codecs1) { + C codec_mutable = codec; + used_payload_types.FindAndSetIdUsed(&codec_mutable); + all_codecs.push_back(codec_mutable); + } + + // Use MergeCodecs to merge the second half of our list as it already checks + // and fixes problems with duplicate payload types. + MergeCodecs<C>(codecs2, &all_codecs, &used_payload_types, field_trials); + + return all_codecs; +} + +// Adds all extensions from `reference_extensions` to `offered_extensions` that +// don't already exist in `offered_extensions` and ensure the IDs don't +// collide. If an extension is added, it's also added to `regular_extensions` or +// `encrypted_extensions`, and if the extension is in `regular_extensions` or +// `encrypted_extensions`, its ID is marked as used in `used_ids`. +// `offered_extensions` is for either audio or video while `regular_extensions` +// and `encrypted_extensions` are used for both audio and video. There could be +// overlap between audio extensions and video extensions. +static void MergeRtpHdrExts(const RtpHeaderExtensions& reference_extensions, + RtpHeaderExtensions* offered_extensions, + RtpHeaderExtensions* regular_extensions, + RtpHeaderExtensions* encrypted_extensions, + UsedRtpHeaderExtensionIds* used_ids) { + for (auto reference_extension : reference_extensions) { + if (!webrtc::RtpExtension::FindHeaderExtensionByUriAndEncryption( + *offered_extensions, reference_extension.uri, + reference_extension.encrypt)) { + if (reference_extension.encrypt) { + const webrtc::RtpExtension* existing = + webrtc::RtpExtension::FindHeaderExtensionByUriAndEncryption( + *encrypted_extensions, reference_extension.uri, + reference_extension.encrypt); + if (existing) { + offered_extensions->push_back(*existing); + } else { + used_ids->FindAndSetIdUsed(&reference_extension); + encrypted_extensions->push_back(reference_extension); + offered_extensions->push_back(reference_extension); + } + } else { + const webrtc::RtpExtension* existing = + webrtc::RtpExtension::FindHeaderExtensionByUriAndEncryption( + *regular_extensions, reference_extension.uri, + reference_extension.encrypt); + if (existing) { + offered_extensions->push_back(*existing); + } else { + used_ids->FindAndSetIdUsed(&reference_extension); + regular_extensions->push_back(reference_extension); + offered_extensions->push_back(reference_extension); + } + } + } + } +} + +static void AddEncryptedVersionsOfHdrExts( + RtpHeaderExtensions* offered_extensions, + RtpHeaderExtensions* encrypted_extensions, + UsedRtpHeaderExtensionIds* used_ids) { + RtpHeaderExtensions encrypted_extensions_to_add; + for (const auto& extension : *offered_extensions) { + // Skip existing encrypted offered extension + if (extension.encrypt) { + continue; + } + + // Skip if we cannot encrypt the extension + if (!webrtc::RtpExtension::IsEncryptionSupported(extension.uri)) { + continue; + } + + // Skip if an encrypted extension with that URI already exists in the + // offered extensions. + const bool have_encrypted_extension = + webrtc::RtpExtension::FindHeaderExtensionByUriAndEncryption( + *offered_extensions, extension.uri, true); + if (have_encrypted_extension) { + continue; + } + + // Determine if a shared encrypted extension with that URI already exists. + const webrtc::RtpExtension* shared_encrypted_extension = + webrtc::RtpExtension::FindHeaderExtensionByUriAndEncryption( + *encrypted_extensions, extension.uri, true); + if (shared_encrypted_extension) { + // Re-use the shared encrypted extension + encrypted_extensions_to_add.push_back(*shared_encrypted_extension); + continue; + } + + // None exists. Create a new shared encrypted extension from the + // non-encrypted one. + webrtc::RtpExtension new_encrypted_extension(extension); + new_encrypted_extension.encrypt = true; + used_ids->FindAndSetIdUsed(&new_encrypted_extension); + encrypted_extensions->push_back(new_encrypted_extension); + encrypted_extensions_to_add.push_back(new_encrypted_extension); + } + + // Append the additional encrypted extensions to be offered + offered_extensions->insert(offered_extensions->end(), + encrypted_extensions_to_add.begin(), + encrypted_extensions_to_add.end()); +} + +// Mostly identical to RtpExtension::FindHeaderExtensionByUri but discards any +// encrypted extensions that this implementation cannot encrypt. +static const webrtc::RtpExtension* FindHeaderExtensionByUriDiscardUnsupported( + const std::vector<webrtc::RtpExtension>& extensions, + absl::string_view uri, + webrtc::RtpExtension::Filter filter) { + // Note: While it's technically possible to decrypt extensions that we don't + // encrypt, the symmetric API of libsrtp does not allow us to supply + // different IDs for encryption/decryption of header extensions depending on + // whether the packet is inbound or outbound. Thereby, we are limited to + // what we can send in encrypted form. + if (!webrtc::RtpExtension::IsEncryptionSupported(uri)) { + // If there's no encryption support and we only want encrypted extensions, + // there's no point in continuing the search here. + if (filter == webrtc::RtpExtension::kRequireEncryptedExtension) { + return nullptr; + } + + // Instruct to only return non-encrypted extensions + filter = webrtc::RtpExtension::Filter::kDiscardEncryptedExtension; + } + + return webrtc::RtpExtension::FindHeaderExtensionByUri(extensions, uri, + filter); +} + +static void NegotiateRtpHeaderExtensions( + const RtpHeaderExtensions& local_extensions, + const RtpHeaderExtensions& offered_extensions, + webrtc::RtpExtension::Filter filter, + RtpHeaderExtensions* negotiated_extensions) { + // TransportSequenceNumberV2 is not offered by default. The special logic for + // the TransportSequenceNumber extensions works as follows: + // Offer Answer + // V1 V1 if in local_extensions. + // V1 and V2 V2 regardless of local_extensions. + // V2 V2 regardless of local_extensions. + const webrtc::RtpExtension* transport_sequence_number_v2_offer = + FindHeaderExtensionByUriDiscardUnsupported( + offered_extensions, + webrtc::RtpExtension::kTransportSequenceNumberV2Uri, filter); + + bool frame_descriptor_in_local = false; + bool dependency_descriptor_in_local = false; + bool abs_capture_time_in_local = false; + + for (const webrtc::RtpExtension& ours : local_extensions) { + if (ours.uri == webrtc::RtpExtension::kGenericFrameDescriptorUri00) + frame_descriptor_in_local = true; + else if (ours.uri == webrtc::RtpExtension::kDependencyDescriptorUri) + dependency_descriptor_in_local = true; + else if (ours.uri == webrtc::RtpExtension::kAbsoluteCaptureTimeUri) + abs_capture_time_in_local = true; + const webrtc::RtpExtension* theirs = + FindHeaderExtensionByUriDiscardUnsupported(offered_extensions, ours.uri, + filter); + if (theirs) { + if (transport_sequence_number_v2_offer && + ours.uri == webrtc::RtpExtension::kTransportSequenceNumberUri) { + // Don't respond to + // http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01 + // if we get an offer including + // http://www.webrtc.org/experiments/rtp-hdrext/transport-wide-cc-02 + continue; + } else { + // We respond with their RTP header extension id. + negotiated_extensions->push_back(*theirs); + } + } + } + + if (transport_sequence_number_v2_offer) { + // Respond that we support kTransportSequenceNumberV2Uri. + negotiated_extensions->push_back(*transport_sequence_number_v2_offer); + } + + // Frame descriptors support. If the extension is not present locally, but is + // in the offer, we add it to the list. + if (!dependency_descriptor_in_local) { + const webrtc::RtpExtension* theirs = + FindHeaderExtensionByUriDiscardUnsupported( + offered_extensions, webrtc::RtpExtension::kDependencyDescriptorUri, + filter); + if (theirs) { + negotiated_extensions->push_back(*theirs); + } + } + if (!frame_descriptor_in_local) { + const webrtc::RtpExtension* theirs = + FindHeaderExtensionByUriDiscardUnsupported( + offered_extensions, + webrtc::RtpExtension::kGenericFrameDescriptorUri00, filter); + if (theirs) { + negotiated_extensions->push_back(*theirs); + } + } + + // Absolute capture time support. If the extension is not present locally, but + // is in the offer, we add it to the list. + if (!abs_capture_time_in_local) { + const webrtc::RtpExtension* theirs = + FindHeaderExtensionByUriDiscardUnsupported( + offered_extensions, webrtc::RtpExtension::kAbsoluteCaptureTimeUri, + filter); + if (theirs) { + negotiated_extensions->push_back(*theirs); + } + } +} + +static void StripCNCodecs(AudioCodecs* audio_codecs) { + audio_codecs->erase(std::remove_if(audio_codecs->begin(), audio_codecs->end(), + [](const AudioCodec& codec) { + return IsComfortNoiseCodec(codec); + }), + audio_codecs->end()); +} + +template <class C> +static bool SetCodecsInAnswer( + const MediaContentDescriptionImpl<C>* offer, + const std::vector<C>& local_codecs, + const MediaDescriptionOptions& media_description_options, + const MediaSessionOptions& session_options, + UniqueRandomIdGenerator* ssrc_generator, + StreamParamsVec* current_streams, + MediaContentDescriptionImpl<C>* answer, + const webrtc::FieldTrialsView& field_trials) { + std::vector<C> negotiated_codecs; + NegotiateCodecs(local_codecs, offer->codecs(), &negotiated_codecs, + media_description_options.codec_preferences.empty(), + &field_trials); + answer->AddCodecs(negotiated_codecs); + answer->set_protocol(offer->protocol()); + if (!AddStreamParams(media_description_options.sender_options, + session_options.rtcp_cname, ssrc_generator, + current_streams, answer, field_trials)) { + return false; // Something went seriously wrong. + } + return true; +} + +// Create a media content to be answered for the given `sender_options` +// according to the given session_options.rtcp_mux, session_options.streams, +// codecs, crypto, and current_streams. If we don't currently have crypto (in +// current_cryptos) and it is enabled (in secure_policy), crypto is created +// (according to crypto_suites). The codecs, rtcp_mux, and crypto are all +// negotiated with the offer. If the negotiation fails, this method returns +// false. The created content is added to the offer. +static bool CreateMediaContentAnswer( + const MediaContentDescription* offer, + const MediaDescriptionOptions& media_description_options, + const MediaSessionOptions& session_options, + const SecurePolicy& sdes_policy, + const CryptoParamsVec* current_cryptos, + const RtpHeaderExtensions& local_rtp_extensions, + UniqueRandomIdGenerator* ssrc_generator, + bool enable_encrypted_rtp_header_extensions, + StreamParamsVec* current_streams, + bool bundle_enabled, + MediaContentDescription* answer) { + answer->set_extmap_allow_mixed_enum(offer->extmap_allow_mixed_enum()); + const webrtc::RtpExtension::Filter extensions_filter = + enable_encrypted_rtp_header_extensions + ? webrtc::RtpExtension::Filter::kPreferEncryptedExtension + : webrtc::RtpExtension::Filter::kDiscardEncryptedExtension; + RtpHeaderExtensions negotiated_rtp_extensions; + NegotiateRtpHeaderExtensions(local_rtp_extensions, + offer->rtp_header_extensions(), + extensions_filter, &negotiated_rtp_extensions); + answer->set_rtp_header_extensions(negotiated_rtp_extensions); + + answer->set_rtcp_mux(session_options.rtcp_mux_enabled && offer->rtcp_mux()); + if (answer->type() == cricket::MEDIA_TYPE_VIDEO) { + answer->set_rtcp_reduced_size(offer->rtcp_reduced_size()); + } + + answer->set_remote_estimate(offer->remote_estimate()); + + if (sdes_policy != SEC_DISABLED) { + CryptoParams crypto; + if (SelectCrypto(offer, bundle_enabled, session_options.crypto_options, + &crypto)) { + if (current_cryptos) { + FindMatchingCrypto(*current_cryptos, crypto, &crypto); + } + answer->AddCrypto(crypto); + } + } + + if (answer->cryptos().empty() && sdes_policy == SEC_REQUIRED) { + return false; + } + + AddSimulcastToMediaDescription(media_description_options, answer); + + answer->set_direction(NegotiateRtpTransceiverDirection( + offer->direction(), media_description_options.direction)); + + return true; +} + +static bool IsMediaProtocolSupported(MediaType type, + const std::string& protocol, + bool secure_transport) { + // Since not all applications serialize and deserialize the media protocol, + // we will have to accept `protocol` to be empty. + if (protocol.empty()) { + return true; + } + + if (type == MEDIA_TYPE_DATA) { + // Check for SCTP + if (secure_transport) { + // Most likely scenarios first. + return IsDtlsSctp(protocol); + } else { + return IsPlainSctp(protocol); + } + } + + // Allow for non-DTLS RTP protocol even when using DTLS because that's what + // JSEP specifies. + if (secure_transport) { + // Most likely scenarios first. + return IsDtlsRtp(protocol) || IsPlainRtp(protocol); + } else { + return IsPlainRtp(protocol); + } +} + +static void SetMediaProtocol(bool secure_transport, + MediaContentDescription* desc) { + if (!desc->cryptos().empty()) + desc->set_protocol(kMediaProtocolSavpf); + else if (secure_transport) + desc->set_protocol(kMediaProtocolDtlsSavpf); + else + desc->set_protocol(kMediaProtocolAvpf); +} + +// Gets the TransportInfo of the given `content_name` from the +// `current_description`. If doesn't exist, returns a new one. +static const TransportDescription* GetTransportDescription( + const std::string& content_name, + const SessionDescription* current_description) { + const TransportDescription* desc = NULL; + if (current_description) { + const TransportInfo* info = + current_description->GetTransportInfoByName(content_name); + if (info) { + desc = &info->description; + } + } + return desc; +} + +// Gets the current DTLS state from the transport description. +static bool IsDtlsActive(const ContentInfo* content, + const SessionDescription* current_description) { + if (!content) { + return false; + } + + size_t msection_index = content - ¤t_description->contents()[0]; + + if (current_description->transport_infos().size() <= msection_index) { + return false; + } + + return current_description->transport_infos()[msection_index] + .description.secure(); +} + +void MediaDescriptionOptions::AddAudioSender( + const std::string& track_id, + const std::vector<std::string>& stream_ids) { + RTC_DCHECK(type == MEDIA_TYPE_AUDIO); + AddSenderInternal(track_id, stream_ids, {}, SimulcastLayerList(), 1); +} + +void MediaDescriptionOptions::AddVideoSender( + const std::string& track_id, + const std::vector<std::string>& stream_ids, + const std::vector<RidDescription>& rids, + const SimulcastLayerList& simulcast_layers, + int num_sim_layers) { + RTC_DCHECK(type == MEDIA_TYPE_VIDEO); + RTC_DCHECK(rids.empty() || num_sim_layers == 0) + << "RIDs are the compliant way to indicate simulcast."; + RTC_DCHECK(ValidateSimulcastLayers(rids, simulcast_layers)); + AddSenderInternal(track_id, stream_ids, rids, simulcast_layers, + num_sim_layers); +} + +void MediaDescriptionOptions::AddSenderInternal( + const std::string& track_id, + const std::vector<std::string>& stream_ids, + const std::vector<RidDescription>& rids, + const SimulcastLayerList& simulcast_layers, + int num_sim_layers) { + // TODO(steveanton): Support any number of stream ids. + RTC_CHECK(stream_ids.size() == 1U); + SenderOptions options; + options.track_id = track_id; + options.stream_ids = stream_ids; + options.simulcast_layers = simulcast_layers; + options.rids = rids; + options.num_sim_layers = num_sim_layers; + sender_options.push_back(options); +} + +bool MediaSessionOptions::HasMediaDescription(MediaType type) const { + return absl::c_any_of( + media_description_options, + [type](const MediaDescriptionOptions& t) { return t.type == type; }); +} + +MediaSessionDescriptionFactory::MediaSessionDescriptionFactory( + const TransportDescriptionFactory* transport_desc_factory, + rtc::UniqueRandomIdGenerator* ssrc_generator) + : ssrc_generator_(ssrc_generator), + transport_desc_factory_(transport_desc_factory) {} + +MediaSessionDescriptionFactory::MediaSessionDescriptionFactory( + cricket::MediaEngineInterface* media_engine, + bool rtx_enabled, + rtc::UniqueRandomIdGenerator* ssrc_generator, + const TransportDescriptionFactory* transport_desc_factory) + : MediaSessionDescriptionFactory(transport_desc_factory, ssrc_generator) { + if (media_engine) { + audio_send_codecs_ = media_engine->voice().send_codecs(); + audio_recv_codecs_ = media_engine->voice().recv_codecs(); + video_send_codecs_ = media_engine->video().send_codecs(rtx_enabled); + video_recv_codecs_ = media_engine->video().recv_codecs(rtx_enabled); + } + ComputeAudioCodecsIntersectionAndUnion(); + ComputeVideoCodecsIntersectionAndUnion(); +} + +const AudioCodecs& MediaSessionDescriptionFactory::audio_sendrecv_codecs() + const { + return audio_sendrecv_codecs_; +} + +const AudioCodecs& MediaSessionDescriptionFactory::audio_send_codecs() const { + return audio_send_codecs_; +} + +const AudioCodecs& MediaSessionDescriptionFactory::audio_recv_codecs() const { + return audio_recv_codecs_; +} + +void MediaSessionDescriptionFactory::set_audio_codecs( + const AudioCodecs& send_codecs, + const AudioCodecs& recv_codecs) { + audio_send_codecs_ = send_codecs; + audio_recv_codecs_ = recv_codecs; + ComputeAudioCodecsIntersectionAndUnion(); +} + +const VideoCodecs& MediaSessionDescriptionFactory::video_sendrecv_codecs() + const { + return video_sendrecv_codecs_; +} + +const VideoCodecs& MediaSessionDescriptionFactory::video_send_codecs() const { + return video_send_codecs_; +} + +const VideoCodecs& MediaSessionDescriptionFactory::video_recv_codecs() const { + return video_recv_codecs_; +} + +void MediaSessionDescriptionFactory::set_video_codecs( + const VideoCodecs& send_codecs, + const VideoCodecs& recv_codecs) { + video_send_codecs_ = send_codecs; + video_recv_codecs_ = recv_codecs; + ComputeVideoCodecsIntersectionAndUnion(); +} + +static void RemoveUnifiedPlanExtensions(RtpHeaderExtensions* extensions) { + RTC_DCHECK(extensions); + + extensions->erase( + std::remove_if(extensions->begin(), extensions->end(), + [](auto extension) { + return extension.uri == webrtc::RtpExtension::kMidUri || + extension.uri == webrtc::RtpExtension::kRidUri || + extension.uri == + webrtc::RtpExtension::kRepairedRidUri; + }), + extensions->end()); +} + +RtpHeaderExtensions +MediaSessionDescriptionFactory::filtered_rtp_header_extensions( + RtpHeaderExtensions extensions) const { + if (!is_unified_plan_) { + RemoveUnifiedPlanExtensions(&extensions); + } + return extensions; +} + +std::unique_ptr<SessionDescription> MediaSessionDescriptionFactory::CreateOffer( + const MediaSessionOptions& session_options, + const SessionDescription* current_description) const { + // Must have options for each existing section. + if (current_description) { + RTC_DCHECK_LE(current_description->contents().size(), + session_options.media_description_options.size()); + } + + IceCredentialsIterator ice_credentials( + session_options.pooled_ice_credentials); + + std::vector<const ContentInfo*> current_active_contents; + if (current_description) { + current_active_contents = + GetActiveContents(*current_description, session_options); + } + + StreamParamsVec current_streams = + GetCurrentStreamParams(current_active_contents); + + AudioCodecs offer_audio_codecs; + VideoCodecs offer_video_codecs; + GetCodecsForOffer(current_active_contents, &offer_audio_codecs, + &offer_video_codecs); + AudioVideoRtpHeaderExtensions extensions_with_ids = + GetOfferedRtpHeaderExtensionsWithIds( + current_active_contents, session_options.offer_extmap_allow_mixed, + session_options.media_description_options); + + auto offer = std::make_unique<SessionDescription>(); + + // Iterate through the media description options, matching with existing media + // descriptions in `current_description`. + size_t msection_index = 0; + for (const MediaDescriptionOptions& media_description_options : + session_options.media_description_options) { + const ContentInfo* current_content = nullptr; + if (current_description && + msection_index < current_description->contents().size()) { + current_content = ¤t_description->contents()[msection_index]; + // Media type must match unless this media section is being recycled. + RTC_DCHECK(current_content->name != media_description_options.mid || + IsMediaContentOfType(current_content, + media_description_options.type)); + } + switch (media_description_options.type) { + case MEDIA_TYPE_AUDIO: + if (!AddAudioContentForOffer(media_description_options, session_options, + current_content, current_description, + extensions_with_ids.audio, + offer_audio_codecs, ¤t_streams, + offer.get(), &ice_credentials)) { + return nullptr; + } + break; + case MEDIA_TYPE_VIDEO: + if (!AddVideoContentForOffer(media_description_options, session_options, + current_content, current_description, + extensions_with_ids.video, + offer_video_codecs, ¤t_streams, + offer.get(), &ice_credentials)) { + return nullptr; + } + break; + case MEDIA_TYPE_DATA: + if (!AddDataContentForOffer(media_description_options, session_options, + current_content, current_description, + ¤t_streams, offer.get(), + &ice_credentials)) { + return nullptr; + } + break; + case MEDIA_TYPE_UNSUPPORTED: + if (!AddUnsupportedContentForOffer( + media_description_options, session_options, current_content, + current_description, offer.get(), &ice_credentials)) { + return nullptr; + } + break; + default: + RTC_DCHECK_NOTREACHED(); + } + ++msection_index; + } + + // Bundle the contents together, if we've been asked to do so, and update any + // parameters that need to be tweaked for BUNDLE. + if (session_options.bundle_enabled) { + ContentGroup offer_bundle(GROUP_TYPE_BUNDLE); + for (const ContentInfo& content : offer->contents()) { + if (content.rejected) { + continue; + } + // TODO(deadbeef): There are conditions that make bundling two media + // descriptions together illegal. For example, they use the same payload + // type to represent different codecs, or same IDs for different header + // extensions. We need to detect this and not try to bundle those media + // descriptions together. + offer_bundle.AddContentName(content.name); + } + if (!offer_bundle.content_names().empty()) { + offer->AddGroup(offer_bundle); + if (!UpdateTransportInfoForBundle(offer_bundle, offer.get())) { + RTC_LOG(LS_ERROR) + << "CreateOffer failed to UpdateTransportInfoForBundle."; + return nullptr; + } + if (!UpdateCryptoParamsForBundle(offer_bundle, offer.get())) { + RTC_LOG(LS_ERROR) + << "CreateOffer failed to UpdateCryptoParamsForBundle."; + return nullptr; + } + } + } + + // The following determines how to signal MSIDs to ensure compatibility with + // older endpoints (in particular, older Plan B endpoints). + if (is_unified_plan_) { + // Be conservative and signal using both a=msid and a=ssrc lines. Unified + // Plan answerers will look at a=msid and Plan B answerers will look at the + // a=ssrc MSID line. + offer->set_msid_signaling(cricket::kMsidSignalingMediaSection | + cricket::kMsidSignalingSsrcAttribute); + } else { + // Plan B always signals MSID using a=ssrc lines. + offer->set_msid_signaling(cricket::kMsidSignalingSsrcAttribute); + } + + offer->set_extmap_allow_mixed(session_options.offer_extmap_allow_mixed); + + return offer; +} + +std::unique_ptr<SessionDescription> +MediaSessionDescriptionFactory::CreateAnswer( + const SessionDescription* offer, + const MediaSessionOptions& session_options, + const SessionDescription* current_description) const { + if (!offer) { + return nullptr; + } + + // Must have options for exactly as many sections as in the offer. + RTC_DCHECK_EQ(offer->contents().size(), + session_options.media_description_options.size()); + + IceCredentialsIterator ice_credentials( + session_options.pooled_ice_credentials); + + std::vector<const ContentInfo*> current_active_contents; + if (current_description) { + current_active_contents = + GetActiveContents(*current_description, session_options); + } + + StreamParamsVec current_streams = + GetCurrentStreamParams(current_active_contents); + + // Get list of all possible codecs that respects existing payload type + // mappings and uses a single payload type space. + // + // Note that these lists may be further filtered for each m= section; this + // step is done just to establish the payload type mappings shared by all + // sections. + AudioCodecs answer_audio_codecs; + VideoCodecs answer_video_codecs; + GetCodecsForAnswer(current_active_contents, *offer, &answer_audio_codecs, + &answer_video_codecs); + + auto answer = std::make_unique<SessionDescription>(); + + // If the offer supports BUNDLE, and we want to use it too, create a BUNDLE + // group in the answer with the appropriate content names. + std::vector<const ContentGroup*> offer_bundles = + offer->GetGroupsByName(GROUP_TYPE_BUNDLE); + // There are as many answer BUNDLE groups as offer BUNDLE groups (even if + // rejected, we respond with an empty group). `offer_bundles`, + // `answer_bundles` and `bundle_transports` share the same size and indices. + std::vector<ContentGroup> answer_bundles; + std::vector<std::unique_ptr<TransportInfo>> bundle_transports; + answer_bundles.reserve(offer_bundles.size()); + bundle_transports.reserve(offer_bundles.size()); + for (size_t i = 0; i < offer_bundles.size(); ++i) { + answer_bundles.emplace_back(GROUP_TYPE_BUNDLE); + bundle_transports.emplace_back(nullptr); + } + + answer->set_extmap_allow_mixed(offer->extmap_allow_mixed()); + + // Iterate through the media description options, matching with existing + // media descriptions in `current_description`. + size_t msection_index = 0; + for (const MediaDescriptionOptions& media_description_options : + session_options.media_description_options) { + const ContentInfo* offer_content = &offer->contents()[msection_index]; + // Media types and MIDs must match between the remote offer and the + // MediaDescriptionOptions. + RTC_DCHECK( + IsMediaContentOfType(offer_content, media_description_options.type)); + RTC_DCHECK(media_description_options.mid == offer_content->name); + // Get the index of the BUNDLE group that this MID belongs to, if any. + absl::optional<size_t> bundle_index; + for (size_t i = 0; i < offer_bundles.size(); ++i) { + if (offer_bundles[i]->HasContentName(media_description_options.mid)) { + bundle_index = i; + break; + } + } + TransportInfo* bundle_transport = + bundle_index.has_value() ? bundle_transports[bundle_index.value()].get() + : nullptr; + + const ContentInfo* current_content = nullptr; + if (current_description && + msection_index < current_description->contents().size()) { + current_content = ¤t_description->contents()[msection_index]; + } + RtpHeaderExtensions header_extensions = RtpHeaderExtensionsFromCapabilities( + UnstoppedRtpHeaderExtensionCapabilities( + media_description_options.header_extensions)); + switch (media_description_options.type) { + case MEDIA_TYPE_AUDIO: + if (!AddAudioContentForAnswer( + media_description_options, session_options, offer_content, + offer, current_content, current_description, bundle_transport, + answer_audio_codecs, header_extensions, ¤t_streams, + answer.get(), &ice_credentials)) { + return nullptr; + } + break; + case MEDIA_TYPE_VIDEO: + if (!AddVideoContentForAnswer( + media_description_options, session_options, offer_content, + offer, current_content, current_description, bundle_transport, + answer_video_codecs, header_extensions, ¤t_streams, + answer.get(), &ice_credentials)) { + return nullptr; + } + break; + case MEDIA_TYPE_DATA: + if (!AddDataContentForAnswer( + media_description_options, session_options, offer_content, + offer, current_content, current_description, bundle_transport, + ¤t_streams, answer.get(), &ice_credentials)) { + return nullptr; + } + break; + case MEDIA_TYPE_UNSUPPORTED: + if (!AddUnsupportedContentForAnswer( + media_description_options, session_options, offer_content, + offer, current_content, current_description, bundle_transport, + answer.get(), &ice_credentials)) { + return nullptr; + } + break; + default: + RTC_DCHECK_NOTREACHED(); + } + ++msection_index; + // See if we can add the newly generated m= section to the BUNDLE group in + // the answer. + ContentInfo& added = answer->contents().back(); + if (!added.rejected && session_options.bundle_enabled && + bundle_index.has_value()) { + // The `bundle_index` is for `media_description_options.mid`. + RTC_DCHECK_EQ(media_description_options.mid, added.name); + answer_bundles[bundle_index.value()].AddContentName(added.name); + bundle_transports[bundle_index.value()].reset( + new TransportInfo(*answer->GetTransportInfoByName(added.name))); + } + } + + // If BUNDLE group(s) were offered, put the same number of BUNDLE groups in + // the answer even if they're empty. RFC5888 says: + // + // A SIP entity that receives an offer that contains an "a=group" line + // with semantics that are understood MUST return an answer that + // contains an "a=group" line with the same semantics. + if (!offer_bundles.empty()) { + for (const ContentGroup& answer_bundle : answer_bundles) { + answer->AddGroup(answer_bundle); + + if (answer_bundle.FirstContentName()) { + // Share the same ICE credentials and crypto params across all contents, + // as BUNDLE requires. + if (!UpdateTransportInfoForBundle(answer_bundle, answer.get())) { + RTC_LOG(LS_ERROR) + << "CreateAnswer failed to UpdateTransportInfoForBundle."; + return NULL; + } + + if (!UpdateCryptoParamsForBundle(answer_bundle, answer.get())) { + RTC_LOG(LS_ERROR) + << "CreateAnswer failed to UpdateCryptoParamsForBundle."; + return NULL; + } + } + } + } + + // The following determines how to signal MSIDs to ensure compatibility with + // older endpoints (in particular, older Plan B endpoints). + if (is_unified_plan_) { + // Unified Plan needs to look at what the offer included to find the most + // compatible answer. + if (offer->msid_signaling() == 0) { + // We end up here in one of three cases: + // 1. An empty offer. We'll reply with an empty answer so it doesn't + // matter what we pick here. + // 2. A data channel only offer. We won't add any MSIDs to the answer so + // it also doesn't matter what we pick here. + // 3. Media that's either sendonly or inactive from the remote endpoint. + // We don't have any information to say whether the endpoint is Plan B + // or Unified Plan, so be conservative and send both. + answer->set_msid_signaling(cricket::kMsidSignalingMediaSection | + cricket::kMsidSignalingSsrcAttribute); + } else if (offer->msid_signaling() == + (cricket::kMsidSignalingMediaSection | + cricket::kMsidSignalingSsrcAttribute)) { + // If both a=msid and a=ssrc MSID signaling methods were used, we're + // probably talking to a Unified Plan endpoint so respond with just + // a=msid. + answer->set_msid_signaling(cricket::kMsidSignalingMediaSection); + } else { + // Otherwise, it's clear which method the offerer is using so repeat that + // back to them. + answer->set_msid_signaling(offer->msid_signaling()); + } + } else { + // Plan B always signals MSID using a=ssrc lines. + answer->set_msid_signaling(cricket::kMsidSignalingSsrcAttribute); + } + + return answer; +} + +const AudioCodecs& MediaSessionDescriptionFactory::GetAudioCodecsForOffer( + const RtpTransceiverDirection& direction) const { + switch (direction) { + // If stream is inactive - generate list as if sendrecv. + case RtpTransceiverDirection::kSendRecv: + case RtpTransceiverDirection::kStopped: + case RtpTransceiverDirection::kInactive: + return audio_sendrecv_codecs_; + case RtpTransceiverDirection::kSendOnly: + return audio_send_codecs_; + case RtpTransceiverDirection::kRecvOnly: + return audio_recv_codecs_; + } + RTC_CHECK_NOTREACHED(); +} + +const AudioCodecs& MediaSessionDescriptionFactory::GetAudioCodecsForAnswer( + const RtpTransceiverDirection& offer, + const RtpTransceiverDirection& answer) const { + switch (answer) { + // For inactive and sendrecv answers, generate lists as if we were to accept + // the offer's direction. See RFC 3264 Section 6.1. + case RtpTransceiverDirection::kSendRecv: + case RtpTransceiverDirection::kStopped: + case RtpTransceiverDirection::kInactive: + return GetAudioCodecsForOffer( + webrtc::RtpTransceiverDirectionReversed(offer)); + case RtpTransceiverDirection::kSendOnly: + return audio_send_codecs_; + case RtpTransceiverDirection::kRecvOnly: + return audio_recv_codecs_; + } + RTC_CHECK_NOTREACHED(); +} + +const VideoCodecs& MediaSessionDescriptionFactory::GetVideoCodecsForOffer( + const RtpTransceiverDirection& direction) const { + switch (direction) { + // If stream is inactive - generate list as if sendrecv. + case RtpTransceiverDirection::kSendRecv: + case RtpTransceiverDirection::kStopped: + case RtpTransceiverDirection::kInactive: + return video_sendrecv_codecs_; + case RtpTransceiverDirection::kSendOnly: + return video_send_codecs_; + case RtpTransceiverDirection::kRecvOnly: + return video_recv_codecs_; + } + RTC_CHECK_NOTREACHED(); +} + +const VideoCodecs& MediaSessionDescriptionFactory::GetVideoCodecsForAnswer( + const RtpTransceiverDirection& offer, + const RtpTransceiverDirection& answer) const { + switch (answer) { + // For inactive and sendrecv answers, generate lists as if we were to accept + // the offer's direction. See RFC 3264 Section 6.1. + case RtpTransceiverDirection::kSendRecv: + case RtpTransceiverDirection::kStopped: + case RtpTransceiverDirection::kInactive: + return GetVideoCodecsForOffer( + webrtc::RtpTransceiverDirectionReversed(offer)); + case RtpTransceiverDirection::kSendOnly: + return video_send_codecs_; + case RtpTransceiverDirection::kRecvOnly: + return video_recv_codecs_; + } + RTC_CHECK_NOTREACHED(); +} + +void MergeCodecsFromDescription( + const std::vector<const ContentInfo*>& current_active_contents, + AudioCodecs* audio_codecs, + VideoCodecs* video_codecs, + UsedPayloadTypes* used_pltypes, + const webrtc::FieldTrialsView* field_trials) { + for (const ContentInfo* content : current_active_contents) { + if (IsMediaContentOfType(content, MEDIA_TYPE_AUDIO)) { + const AudioContentDescription* audio = + content->media_description()->as_audio(); + MergeCodecs<AudioCodec>(audio->codecs(), audio_codecs, used_pltypes, + field_trials); + } else if (IsMediaContentOfType(content, MEDIA_TYPE_VIDEO)) { + const VideoContentDescription* video = + content->media_description()->as_video(); + MergeCodecs<VideoCodec>(video->codecs(), video_codecs, used_pltypes, + field_trials); + } + } +} + +// Getting codecs for an offer involves these steps: +// +// 1. Construct payload type -> codec mappings for current description. +// 2. Add any reference codecs that weren't already present +// 3. For each individual media description (m= section), filter codecs based +// on the directional attribute (happens in another method). +void MediaSessionDescriptionFactory::GetCodecsForOffer( + const std::vector<const ContentInfo*>& current_active_contents, + AudioCodecs* audio_codecs, + VideoCodecs* video_codecs) const { + const webrtc::FieldTrialsView* field_trials = + &transport_desc_factory_->trials(); + // First - get all codecs from the current description if the media type + // is used. Add them to `used_pltypes` so the payload type is not reused if a + // new media type is added. + UsedPayloadTypes used_pltypes; + MergeCodecsFromDescription(current_active_contents, audio_codecs, + video_codecs, &used_pltypes, field_trials); + + // Add our codecs that are not in the current description. + MergeCodecs<AudioCodec>(all_audio_codecs_, audio_codecs, &used_pltypes, + field_trials); + MergeCodecs<VideoCodec>(all_video_codecs_, video_codecs, &used_pltypes, + field_trials); +} + +// Getting codecs for an answer involves these steps: +// +// 1. Construct payload type -> codec mappings for current description. +// 2. Add any codecs from the offer that weren't already present. +// 3. Add any remaining codecs that weren't already present. +// 4. For each individual media description (m= section), filter codecs based +// on the directional attribute (happens in another method). +void MediaSessionDescriptionFactory::GetCodecsForAnswer( + const std::vector<const ContentInfo*>& current_active_contents, + const SessionDescription& remote_offer, + AudioCodecs* audio_codecs, + VideoCodecs* video_codecs) const { + const webrtc::FieldTrialsView* field_trials = + &transport_desc_factory_->trials(); + // First - get all codecs from the current description if the media type + // is used. Add them to `used_pltypes` so the payload type is not reused if a + // new media type is added. + UsedPayloadTypes used_pltypes; + MergeCodecsFromDescription(current_active_contents, audio_codecs, + video_codecs, &used_pltypes, field_trials); + + // Second - filter out codecs that we don't support at all and should ignore. + AudioCodecs filtered_offered_audio_codecs; + VideoCodecs filtered_offered_video_codecs; + for (const ContentInfo& content : remote_offer.contents()) { + if (IsMediaContentOfType(&content, MEDIA_TYPE_AUDIO)) { + const AudioContentDescription* audio = + content.media_description()->as_audio(); + for (const AudioCodec& offered_audio_codec : audio->codecs()) { + if (!FindMatchingCodec<AudioCodec>( + audio->codecs(), filtered_offered_audio_codecs, + offered_audio_codec, nullptr, field_trials) && + FindMatchingCodec<AudioCodec>(audio->codecs(), all_audio_codecs_, + offered_audio_codec, nullptr, + field_trials)) { + filtered_offered_audio_codecs.push_back(offered_audio_codec); + } + } + } else if (IsMediaContentOfType(&content, MEDIA_TYPE_VIDEO)) { + const VideoContentDescription* video = + content.media_description()->as_video(); + for (const VideoCodec& offered_video_codec : video->codecs()) { + if (!FindMatchingCodec<VideoCodec>( + video->codecs(), filtered_offered_video_codecs, + offered_video_codec, nullptr, field_trials) && + FindMatchingCodec<VideoCodec>(video->codecs(), all_video_codecs_, + offered_video_codec, nullptr, + field_trials)) { + filtered_offered_video_codecs.push_back(offered_video_codec); + } + } + } + } + + // Add codecs that are not in the current description but were in + // `remote_offer`. + MergeCodecs<AudioCodec>(filtered_offered_audio_codecs, audio_codecs, + &used_pltypes, field_trials); + MergeCodecs<VideoCodec>(filtered_offered_video_codecs, video_codecs, + &used_pltypes, field_trials); +} + +MediaSessionDescriptionFactory::AudioVideoRtpHeaderExtensions +MediaSessionDescriptionFactory::GetOfferedRtpHeaderExtensionsWithIds( + const std::vector<const ContentInfo*>& current_active_contents, + bool extmap_allow_mixed, + const std::vector<MediaDescriptionOptions>& media_description_options) + const { + // All header extensions allocated from the same range to avoid potential + // issues when using BUNDLE. + + // Strictly speaking the SDP attribute extmap_allow_mixed signals that the + // receiver supports an RTP stream where one- and two-byte RTP header + // extensions are mixed. For backwards compatibility reasons it's used in + // WebRTC to signal that two-byte RTP header extensions are supported. + UsedRtpHeaderExtensionIds used_ids( + extmap_allow_mixed ? UsedRtpHeaderExtensionIds::IdDomain::kTwoByteAllowed + : UsedRtpHeaderExtensionIds::IdDomain::kOneByteOnly); + RtpHeaderExtensions all_regular_extensions; + RtpHeaderExtensions all_encrypted_extensions; + + AudioVideoRtpHeaderExtensions offered_extensions; + // First - get all extensions from the current description if the media type + // is used. + // Add them to `used_ids` so the local ids are not reused if a new media + // type is added. + for (const ContentInfo* content : current_active_contents) { + if (IsMediaContentOfType(content, MEDIA_TYPE_AUDIO)) { + const AudioContentDescription* audio = + content->media_description()->as_audio(); + MergeRtpHdrExts(audio->rtp_header_extensions(), &offered_extensions.audio, + &all_regular_extensions, &all_encrypted_extensions, + &used_ids); + } else if (IsMediaContentOfType(content, MEDIA_TYPE_VIDEO)) { + const VideoContentDescription* video = + content->media_description()->as_video(); + MergeRtpHdrExts(video->rtp_header_extensions(), &offered_extensions.video, + &all_regular_extensions, &all_encrypted_extensions, + &used_ids); + } + } + + // Add all encountered header extensions in the media description options that + // are not in the current description. + + for (const auto& entry : media_description_options) { + RtpHeaderExtensions filtered_extensions = + filtered_rtp_header_extensions(UnstoppedOrPresentRtpHeaderExtensions( + entry.header_extensions, all_regular_extensions, + all_encrypted_extensions)); + if (entry.type == MEDIA_TYPE_AUDIO) + MergeRtpHdrExts(filtered_extensions, &offered_extensions.audio, + &all_regular_extensions, &all_encrypted_extensions, + &used_ids); + else if (entry.type == MEDIA_TYPE_VIDEO) + MergeRtpHdrExts(filtered_extensions, &offered_extensions.video, + &all_regular_extensions, &all_encrypted_extensions, + &used_ids); + } + // TODO(jbauch): Support adding encrypted header extensions to existing + // sessions. + if (enable_encrypted_rtp_header_extensions_ && + current_active_contents.empty()) { + AddEncryptedVersionsOfHdrExts(&offered_extensions.audio, + &all_encrypted_extensions, &used_ids); + AddEncryptedVersionsOfHdrExts(&offered_extensions.video, + &all_encrypted_extensions, &used_ids); + } + return offered_extensions; +} + +bool MediaSessionDescriptionFactory::AddTransportOffer( + const std::string& content_name, + const TransportOptions& transport_options, + const SessionDescription* current_desc, + SessionDescription* offer_desc, + IceCredentialsIterator* ice_credentials) const { + if (!transport_desc_factory_) + return false; + const TransportDescription* current_tdesc = + GetTransportDescription(content_name, current_desc); + std::unique_ptr<TransportDescription> new_tdesc( + transport_desc_factory_->CreateOffer(transport_options, current_tdesc, + ice_credentials)); + if (!new_tdesc) { + RTC_LOG(LS_ERROR) << "Failed to AddTransportOffer, content name=" + << content_name; + } + offer_desc->AddTransportInfo(TransportInfo(content_name, *new_tdesc)); + return true; +} + +std::unique_ptr<TransportDescription> +MediaSessionDescriptionFactory::CreateTransportAnswer( + const std::string& content_name, + const SessionDescription* offer_desc, + const TransportOptions& transport_options, + const SessionDescription* current_desc, + bool require_transport_attributes, + IceCredentialsIterator* ice_credentials) const { + if (!transport_desc_factory_) + return NULL; + const TransportDescription* offer_tdesc = + GetTransportDescription(content_name, offer_desc); + const TransportDescription* current_tdesc = + GetTransportDescription(content_name, current_desc); + return transport_desc_factory_->CreateAnswer(offer_tdesc, transport_options, + require_transport_attributes, + current_tdesc, ice_credentials); +} + +bool MediaSessionDescriptionFactory::AddTransportAnswer( + const std::string& content_name, + const TransportDescription& transport_desc, + SessionDescription* answer_desc) const { + answer_desc->AddTransportInfo(TransportInfo(content_name, transport_desc)); + return true; +} + +// `audio_codecs` = set of all possible codecs that can be used, with correct +// payload type mappings +// +// `supported_audio_codecs` = set of codecs that are supported for the direction +// of this m= section +// +// acd->codecs() = set of previously negotiated codecs for this m= section +// +// The payload types should come from audio_codecs, but the order should come +// from acd->codecs() and then supported_codecs, to ensure that re-offers don't +// change existing codec priority, and that new codecs are added with the right +// priority. +bool MediaSessionDescriptionFactory::AddAudioContentForOffer( + const MediaDescriptionOptions& media_description_options, + const MediaSessionOptions& session_options, + const ContentInfo* current_content, + const SessionDescription* current_description, + const RtpHeaderExtensions& audio_rtp_extensions, + const AudioCodecs& audio_codecs, + StreamParamsVec* current_streams, + SessionDescription* desc, + IceCredentialsIterator* ice_credentials) const { + const webrtc::FieldTrialsView* field_trials = + &transport_desc_factory_->trials(); + // Filter audio_codecs (which includes all codecs, with correctly remapped + // payload types) based on transceiver direction. + const AudioCodecs& supported_audio_codecs = + GetAudioCodecsForOffer(media_description_options.direction); + + AudioCodecs filtered_codecs; + + if (!media_description_options.codec_preferences.empty()) { + // Add the codecs from the current transceiver's codec preferences. + // They override any existing codecs from previous negotiations. + filtered_codecs = MatchCodecPreference( + media_description_options.codec_preferences, audio_codecs, + supported_audio_codecs, field_trials); + } else { + // Add the codecs from current content if it exists and is not rejected nor + // recycled. + if (current_content && !current_content->rejected && + current_content->name == media_description_options.mid) { + RTC_CHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_AUDIO)); + const AudioContentDescription* acd = + current_content->media_description()->as_audio(); + for (const AudioCodec& codec : acd->codecs()) { + if (FindMatchingCodec<AudioCodec>(acd->codecs(), audio_codecs, codec, + nullptr, field_trials)) { + filtered_codecs.push_back(codec); + } + } + } + // Add other supported audio codecs. + AudioCodec found_codec; + for (const AudioCodec& codec : supported_audio_codecs) { + if (FindMatchingCodec<AudioCodec>(supported_audio_codecs, audio_codecs, + codec, &found_codec, field_trials) && + !FindMatchingCodec<AudioCodec>(supported_audio_codecs, + filtered_codecs, codec, nullptr, + field_trials)) { + // Use the `found_codec` from `audio_codecs` because it has the + // correctly mapped payload type. + filtered_codecs.push_back(found_codec); + } + } + } + if (!session_options.vad_enabled) { + // If application doesn't want CN codecs in offer. + StripCNCodecs(&filtered_codecs); + } + + cricket::SecurePolicy sdes_policy = + IsDtlsActive(current_content, current_description) ? cricket::SEC_DISABLED + : secure(); + + auto audio = std::make_unique<AudioContentDescription>(); + std::vector<std::string> crypto_suites; + GetSupportedAudioSdesCryptoSuiteNames(session_options.crypto_options, + &crypto_suites); + if (!CreateMediaContentOffer( + media_description_options, session_options, filtered_codecs, + sdes_policy, GetCryptos(current_content), crypto_suites, + audio_rtp_extensions, ssrc_generator(), current_streams, audio.get(), + transport_desc_factory_->trials())) { + return false; + } + + bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED); + SetMediaProtocol(secure_transport, audio.get()); + + audio->set_direction(media_description_options.direction); + + desc->AddContent(media_description_options.mid, MediaProtocolType::kRtp, + media_description_options.stopped, std::move(audio)); + if (!AddTransportOffer(media_description_options.mid, + media_description_options.transport_options, + current_description, desc, ice_credentials)) { + return false; + } + + return true; +} + +// TODO(kron): This function is very similar to AddAudioContentForOffer. +// Refactor to reuse shared code. +bool MediaSessionDescriptionFactory::AddVideoContentForOffer( + const MediaDescriptionOptions& media_description_options, + const MediaSessionOptions& session_options, + const ContentInfo* current_content, + const SessionDescription* current_description, + const RtpHeaderExtensions& video_rtp_extensions, + const VideoCodecs& video_codecs, + StreamParamsVec* current_streams, + SessionDescription* desc, + IceCredentialsIterator* ice_credentials) const { + const webrtc::FieldTrialsView* field_trials = + &transport_desc_factory_->trials(); + // Filter video_codecs (which includes all codecs, with correctly remapped + // payload types) based on transceiver direction. + const VideoCodecs& supported_video_codecs = + GetVideoCodecsForOffer(media_description_options.direction); + + VideoCodecs filtered_codecs; + + if (!media_description_options.codec_preferences.empty()) { + // Add the codecs from the current transceiver's codec preferences. + // They override any existing codecs from previous negotiations. + filtered_codecs = MatchCodecPreference( + media_description_options.codec_preferences, video_codecs, + supported_video_codecs, field_trials); + } else { + // Add the codecs from current content if it exists and is not rejected nor + // recycled. + if (current_content && !current_content->rejected && + current_content->name == media_description_options.mid) { + RTC_CHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_VIDEO)); + const VideoContentDescription* vcd = + current_content->media_description()->as_video(); + for (const VideoCodec& codec : vcd->codecs()) { + if (FindMatchingCodec<VideoCodec>(vcd->codecs(), video_codecs, codec, + nullptr, field_trials)) { + filtered_codecs.push_back(codec); + } + } + } + // Add other supported video codecs. + VideoCodec found_codec; + for (const VideoCodec& codec : supported_video_codecs) { + if (FindMatchingCodec<VideoCodec>(supported_video_codecs, video_codecs, + codec, &found_codec, field_trials) && + !FindMatchingCodec<VideoCodec>(supported_video_codecs, + filtered_codecs, codec, nullptr, + field_trials)) { + // Use the `found_codec` from `video_codecs` because it has the + // correctly mapped payload type. + if (IsRtxCodec(codec)) { + // For RTX we might need to adjust the apt parameter if we got a + // remote offer without RTX for a codec for which we support RTX. + auto referenced_codec = + GetAssociatedCodecForRtx(supported_video_codecs, codec); + RTC_DCHECK(referenced_codec); + + // Find the codec we should be referencing and point to it. + VideoCodec changed_referenced_codec; + if (FindMatchingCodec<VideoCodec>( + supported_video_codecs, filtered_codecs, *referenced_codec, + &changed_referenced_codec, field_trials)) { + found_codec.SetParam(kCodecParamAssociatedPayloadType, + changed_referenced_codec.id); + } + } + filtered_codecs.push_back(found_codec); + } + } + } + + if (session_options.raw_packetization_for_video) { + for (VideoCodec& codec : filtered_codecs) { + if (codec.GetCodecType() == VideoCodec::CODEC_VIDEO) { + codec.packetization = kPacketizationParamRaw; + } + } + } + + cricket::SecurePolicy sdes_policy = + IsDtlsActive(current_content, current_description) ? cricket::SEC_DISABLED + : secure(); + auto video = std::make_unique<VideoContentDescription>(); + std::vector<std::string> crypto_suites; + GetSupportedVideoSdesCryptoSuiteNames(session_options.crypto_options, + &crypto_suites); + if (!CreateMediaContentOffer( + media_description_options, session_options, filtered_codecs, + sdes_policy, GetCryptos(current_content), crypto_suites, + video_rtp_extensions, ssrc_generator(), current_streams, video.get(), + transport_desc_factory_->trials())) { + return false; + } + + video->set_bandwidth(kAutoBandwidth); + + bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED); + SetMediaProtocol(secure_transport, video.get()); + + video->set_direction(media_description_options.direction); + + desc->AddContent(media_description_options.mid, MediaProtocolType::kRtp, + media_description_options.stopped, std::move(video)); + if (!AddTransportOffer(media_description_options.mid, + media_description_options.transport_options, + current_description, desc, ice_credentials)) { + return false; + } + + return true; +} + +bool MediaSessionDescriptionFactory::AddDataContentForOffer( + const MediaDescriptionOptions& media_description_options, + const MediaSessionOptions& session_options, + const ContentInfo* current_content, + const SessionDescription* current_description, + StreamParamsVec* current_streams, + SessionDescription* desc, + IceCredentialsIterator* ice_credentials) const { + auto data = std::make_unique<SctpDataContentDescription>(); + + bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED); + + cricket::SecurePolicy sdes_policy = + IsDtlsActive(current_content, current_description) ? cricket::SEC_DISABLED + : secure(); + std::vector<std::string> crypto_suites; + // SDES doesn't make sense for SCTP, so we disable it, and we only + // get SDES crypto suites for RTP-based data channels. + sdes_policy = cricket::SEC_DISABLED; + // Unlike SetMediaProtocol below, we need to set the protocol + // before we call CreateMediaContentOffer. Otherwise, + // CreateMediaContentOffer won't know this is SCTP and will + // generate SSRCs rather than SIDs. + data->set_protocol(secure_transport ? kMediaProtocolUdpDtlsSctp + : kMediaProtocolSctp); + data->set_use_sctpmap(session_options.use_obsolete_sctp_sdp); + data->set_max_message_size(kSctpSendBufferSize); + + if (!CreateContentOffer(media_description_options, session_options, + sdes_policy, GetCryptos(current_content), + crypto_suites, RtpHeaderExtensions(), + ssrc_generator(), current_streams, data.get())) { + return false; + } + + desc->AddContent(media_description_options.mid, MediaProtocolType::kSctp, + media_description_options.stopped, std::move(data)); + if (!AddTransportOffer(media_description_options.mid, + media_description_options.transport_options, + current_description, desc, ice_credentials)) { + return false; + } + return true; +} + +bool MediaSessionDescriptionFactory::AddUnsupportedContentForOffer( + const MediaDescriptionOptions& media_description_options, + const MediaSessionOptions& session_options, + const ContentInfo* current_content, + const SessionDescription* current_description, + SessionDescription* desc, + IceCredentialsIterator* ice_credentials) const { + RTC_CHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_UNSUPPORTED)); + + const UnsupportedContentDescription* current_unsupported_description = + current_content->media_description()->as_unsupported(); + auto unsupported = std::make_unique<UnsupportedContentDescription>( + current_unsupported_description->media_type()); + unsupported->set_protocol(current_content->media_description()->protocol()); + desc->AddContent(media_description_options.mid, MediaProtocolType::kOther, + /*rejected=*/true, std::move(unsupported)); + + if (!AddTransportOffer(media_description_options.mid, + media_description_options.transport_options, + current_description, desc, ice_credentials)) { + return false; + } + return true; +} + +// `audio_codecs` = set of all possible codecs that can be used, with correct +// payload type mappings +// +// `supported_audio_codecs` = set of codecs that are supported for the direction +// of this m= section +// +// acd->codecs() = set of previously negotiated codecs for this m= section +// +// The payload types should come from audio_codecs, but the order should come +// from acd->codecs() and then supported_codecs, to ensure that re-offers don't +// change existing codec priority, and that new codecs are added with the right +// priority. +bool MediaSessionDescriptionFactory::AddAudioContentForAnswer( + const MediaDescriptionOptions& media_description_options, + const MediaSessionOptions& session_options, + const ContentInfo* offer_content, + const SessionDescription* offer_description, + const ContentInfo* current_content, + const SessionDescription* current_description, + const TransportInfo* bundle_transport, + const AudioCodecs& audio_codecs, + const RtpHeaderExtensions& default_audio_rtp_header_extensions, + StreamParamsVec* current_streams, + SessionDescription* answer, + IceCredentialsIterator* ice_credentials) const { + const webrtc::FieldTrialsView* field_trials = + &transport_desc_factory_->trials(); + RTC_CHECK(IsMediaContentOfType(offer_content, MEDIA_TYPE_AUDIO)); + const AudioContentDescription* offer_audio_description = + offer_content->media_description()->as_audio(); + + std::unique_ptr<TransportDescription> audio_transport = CreateTransportAnswer( + media_description_options.mid, offer_description, + media_description_options.transport_options, current_description, + bundle_transport != nullptr, ice_credentials); + if (!audio_transport) { + return false; + } + + // Pick codecs based on the requested communications direction in the offer + // and the selected direction in the answer. + // Note these will be filtered one final time in CreateMediaContentAnswer. + auto wants_rtd = media_description_options.direction; + auto offer_rtd = offer_audio_description->direction(); + auto answer_rtd = NegotiateRtpTransceiverDirection(offer_rtd, wants_rtd); + AudioCodecs supported_audio_codecs = + GetAudioCodecsForAnswer(offer_rtd, answer_rtd); + + AudioCodecs filtered_codecs; + + if (!media_description_options.codec_preferences.empty()) { + filtered_codecs = MatchCodecPreference( + media_description_options.codec_preferences, audio_codecs, + supported_audio_codecs, field_trials); + } else { + // Add the codecs from current content if it exists and is not rejected nor + // recycled. + if (current_content && !current_content->rejected && + current_content->name == media_description_options.mid) { + RTC_CHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_AUDIO)); + const AudioContentDescription* acd = + current_content->media_description()->as_audio(); + for (const AudioCodec& codec : acd->codecs()) { + if (FindMatchingCodec<AudioCodec>(acd->codecs(), audio_codecs, codec, + nullptr, field_trials)) { + filtered_codecs.push_back(codec); + } + } + } + // Add other supported audio codecs. + for (const AudioCodec& codec : supported_audio_codecs) { + if (FindMatchingCodec<AudioCodec>(supported_audio_codecs, audio_codecs, + codec, nullptr, field_trials) && + !FindMatchingCodec<AudioCodec>(supported_audio_codecs, + filtered_codecs, codec, nullptr, + field_trials)) { + // We should use the local codec with local parameters and the codec id + // would be correctly mapped in `NegotiateCodecs`. + filtered_codecs.push_back(codec); + } + } + } + if (!session_options.vad_enabled) { + // If application doesn't want CN codecs in answer. + StripCNCodecs(&filtered_codecs); + } + + // Determine if we have media codecs in common. + bool has_common_media_codecs = + std::find_if(filtered_codecs.begin(), filtered_codecs.end(), + [](const AudioCodec& c) { + return !(IsRedCodec(c) || IsComfortNoiseCodec(c)); + }) != filtered_codecs.end(); + + bool bundle_enabled = offer_description->HasGroup(GROUP_TYPE_BUNDLE) && + session_options.bundle_enabled; + auto audio_answer = std::make_unique<AudioContentDescription>(); + // Do not require or create SDES cryptos if DTLS is used. + cricket::SecurePolicy sdes_policy = + audio_transport->secure() ? cricket::SEC_DISABLED : secure(); + if (!SetCodecsInAnswer(offer_audio_description, filtered_codecs, + media_description_options, session_options, + ssrc_generator(), current_streams, audio_answer.get(), + transport_desc_factory_->trials())) { + return false; + } + if (!CreateMediaContentAnswer( + offer_audio_description, media_description_options, session_options, + sdes_policy, GetCryptos(current_content), + filtered_rtp_header_extensions(default_audio_rtp_header_extensions), + ssrc_generator(), enable_encrypted_rtp_header_extensions_, + current_streams, bundle_enabled, audio_answer.get())) { + return false; // Fails the session setup. + } + + bool secure = bundle_transport ? bundle_transport->description.secure() + : audio_transport->secure(); + bool rejected = media_description_options.stopped || + offer_content->rejected || !has_common_media_codecs || + !IsMediaProtocolSupported(MEDIA_TYPE_AUDIO, + audio_answer->protocol(), secure); + if (!AddTransportAnswer(media_description_options.mid, + *(audio_transport.get()), answer)) { + return false; + } + + if (rejected) { + RTC_LOG(LS_INFO) << "Audio m= section '" << media_description_options.mid + << "' being rejected in answer."; + } + + answer->AddContent(media_description_options.mid, offer_content->type, + rejected, std::move(audio_answer)); + return true; +} + +// TODO(kron): This function is very similar to AddAudioContentForAnswer. +// Refactor to reuse shared code. +bool MediaSessionDescriptionFactory::AddVideoContentForAnswer( + const MediaDescriptionOptions& media_description_options, + const MediaSessionOptions& session_options, + const ContentInfo* offer_content, + const SessionDescription* offer_description, + const ContentInfo* current_content, + const SessionDescription* current_description, + const TransportInfo* bundle_transport, + const VideoCodecs& video_codecs, + const RtpHeaderExtensions& default_video_rtp_header_extensions, + StreamParamsVec* current_streams, + SessionDescription* answer, + IceCredentialsIterator* ice_credentials) const { + const webrtc::FieldTrialsView* field_trials = + &transport_desc_factory_->trials(); + RTC_CHECK(IsMediaContentOfType(offer_content, MEDIA_TYPE_VIDEO)); + const VideoContentDescription* offer_video_description = + offer_content->media_description()->as_video(); + + std::unique_ptr<TransportDescription> video_transport = CreateTransportAnswer( + media_description_options.mid, offer_description, + media_description_options.transport_options, current_description, + bundle_transport != nullptr, ice_credentials); + if (!video_transport) { + return false; + } + + // Pick codecs based on the requested communications direction in the offer + // and the selected direction in the answer. + // Note these will be filtered one final time in CreateMediaContentAnswer. + auto wants_rtd = media_description_options.direction; + auto offer_rtd = offer_video_description->direction(); + auto answer_rtd = NegotiateRtpTransceiverDirection(offer_rtd, wants_rtd); + VideoCodecs supported_video_codecs = + GetVideoCodecsForAnswer(offer_rtd, answer_rtd); + + VideoCodecs filtered_codecs; + + if (!media_description_options.codec_preferences.empty()) { + filtered_codecs = MatchCodecPreference( + media_description_options.codec_preferences, video_codecs, + supported_video_codecs, field_trials); + } else { + // Add the codecs from current content if it exists and is not rejected nor + // recycled. + if (current_content && !current_content->rejected && + current_content->name == media_description_options.mid) { + RTC_CHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_VIDEO)); + const VideoContentDescription* vcd = + current_content->media_description()->as_video(); + for (const VideoCodec& codec : vcd->codecs()) { + if (FindMatchingCodec<VideoCodec>(vcd->codecs(), video_codecs, codec, + nullptr, field_trials)) { + filtered_codecs.push_back(codec); + } + } + } + + // Add other supported video codecs. + VideoCodecs other_video_codecs; + for (const VideoCodec& codec : supported_video_codecs) { + if (FindMatchingCodec<VideoCodec>(supported_video_codecs, video_codecs, + codec, nullptr, field_trials) && + !FindMatchingCodec<VideoCodec>(supported_video_codecs, + filtered_codecs, codec, nullptr, + field_trials)) { + // We should use the local codec with local parameters and the codec id + // would be correctly mapped in `NegotiateCodecs`. + other_video_codecs.push_back(codec); + } + } + + // Use ComputeCodecsUnion to avoid having duplicate payload IDs + filtered_codecs = ComputeCodecsUnion<VideoCodec>( + filtered_codecs, other_video_codecs, field_trials); + } + // Determine if we have media codecs in common. + bool has_common_media_codecs = + std::find_if( + filtered_codecs.begin(), filtered_codecs.end(), + [](const VideoCodec& c) { + return !(IsRedCodec(c) || IsUlpfecCodec(c) || IsFlexfecCodec(c)); + }) != filtered_codecs.end(); + + if (session_options.raw_packetization_for_video) { + for (VideoCodec& codec : filtered_codecs) { + if (codec.GetCodecType() == VideoCodec::CODEC_VIDEO) { + codec.packetization = kPacketizationParamRaw; + } + } + } + + bool bundle_enabled = offer_description->HasGroup(GROUP_TYPE_BUNDLE) && + session_options.bundle_enabled; + auto video_answer = std::make_unique<VideoContentDescription>(); + // Do not require or create SDES cryptos if DTLS is used. + cricket::SecurePolicy sdes_policy = + video_transport->secure() ? cricket::SEC_DISABLED : secure(); + if (!SetCodecsInAnswer(offer_video_description, filtered_codecs, + media_description_options, session_options, + ssrc_generator(), current_streams, video_answer.get(), + transport_desc_factory_->trials())) { + return false; + } + if (!CreateMediaContentAnswer( + offer_video_description, media_description_options, session_options, + sdes_policy, GetCryptos(current_content), + filtered_rtp_header_extensions(default_video_rtp_header_extensions), + ssrc_generator(), enable_encrypted_rtp_header_extensions_, + current_streams, bundle_enabled, video_answer.get())) { + return false; // Failed the session setup. + } + bool secure = bundle_transport ? bundle_transport->description.secure() + : video_transport->secure(); + bool rejected = media_description_options.stopped || + offer_content->rejected || !has_common_media_codecs || + !IsMediaProtocolSupported(MEDIA_TYPE_VIDEO, + video_answer->protocol(), secure); + if (!AddTransportAnswer(media_description_options.mid, + *(video_transport.get()), answer)) { + return false; + } + + if (!rejected) { + video_answer->set_bandwidth(kAutoBandwidth); + } else { + RTC_LOG(LS_INFO) << "Video m= section '" << media_description_options.mid + << "' being rejected in answer."; + } + answer->AddContent(media_description_options.mid, offer_content->type, + rejected, std::move(video_answer)); + return true; +} + +bool MediaSessionDescriptionFactory::AddDataContentForAnswer( + const MediaDescriptionOptions& media_description_options, + const MediaSessionOptions& session_options, + const ContentInfo* offer_content, + const SessionDescription* offer_description, + const ContentInfo* current_content, + const SessionDescription* current_description, + const TransportInfo* bundle_transport, + StreamParamsVec* current_streams, + SessionDescription* answer, + IceCredentialsIterator* ice_credentials) const { + std::unique_ptr<TransportDescription> data_transport = CreateTransportAnswer( + media_description_options.mid, offer_description, + media_description_options.transport_options, current_description, + bundle_transport != nullptr, ice_credentials); + if (!data_transport) { + return false; + } + + // Do not require or create SDES cryptos if DTLS is used. + cricket::SecurePolicy sdes_policy = + data_transport->secure() ? cricket::SEC_DISABLED : secure(); + bool bundle_enabled = offer_description->HasGroup(GROUP_TYPE_BUNDLE) && + session_options.bundle_enabled; + RTC_CHECK(IsMediaContentOfType(offer_content, MEDIA_TYPE_DATA)); + std::unique_ptr<MediaContentDescription> data_answer; + if (offer_content->media_description()->as_sctp()) { + // SCTP data content + data_answer = std::make_unique<SctpDataContentDescription>(); + const SctpDataContentDescription* offer_data_description = + offer_content->media_description()->as_sctp(); + // Respond with the offerer's proto, whatever it is. + data_answer->as_sctp()->set_protocol(offer_data_description->protocol()); + // Respond with our max message size or the remote max messsage size, + // whichever is smaller. + // 0 is treated specially - it means "I can accept any size". Since + // we do not implement infinite size messages, reply with + // kSctpSendBufferSize. + if (offer_data_description->max_message_size() == 0) { + data_answer->as_sctp()->set_max_message_size(kSctpSendBufferSize); + } else { + data_answer->as_sctp()->set_max_message_size(std::min( + offer_data_description->max_message_size(), kSctpSendBufferSize)); + } + if (!CreateMediaContentAnswer( + offer_data_description, media_description_options, session_options, + sdes_policy, GetCryptos(current_content), RtpHeaderExtensions(), + ssrc_generator(), enable_encrypted_rtp_header_extensions_, + current_streams, bundle_enabled, data_answer.get())) { + return false; // Fails the session setup. + } + // Respond with sctpmap if the offer uses sctpmap. + bool offer_uses_sctpmap = offer_data_description->use_sctpmap(); + data_answer->as_sctp()->set_use_sctpmap(offer_uses_sctpmap); + } else { + RTC_DCHECK_NOTREACHED() << "Non-SCTP data content found"; + } + + bool secure = bundle_transport ? bundle_transport->description.secure() + : data_transport->secure(); + + bool rejected = media_description_options.stopped || + offer_content->rejected || + !IsMediaProtocolSupported(MEDIA_TYPE_DATA, + data_answer->protocol(), secure); + if (!AddTransportAnswer(media_description_options.mid, + *(data_transport.get()), answer)) { + return false; + } + + answer->AddContent(media_description_options.mid, offer_content->type, + rejected, std::move(data_answer)); + return true; +} + +bool MediaSessionDescriptionFactory::AddUnsupportedContentForAnswer( + const MediaDescriptionOptions& media_description_options, + const MediaSessionOptions& session_options, + const ContentInfo* offer_content, + const SessionDescription* offer_description, + const ContentInfo* current_content, + const SessionDescription* current_description, + const TransportInfo* bundle_transport, + SessionDescription* answer, + IceCredentialsIterator* ice_credentials) const { + std::unique_ptr<TransportDescription> unsupported_transport = + CreateTransportAnswer(media_description_options.mid, offer_description, + media_description_options.transport_options, + current_description, bundle_transport != nullptr, + ice_credentials); + if (!unsupported_transport) { + return false; + } + RTC_CHECK(IsMediaContentOfType(offer_content, MEDIA_TYPE_UNSUPPORTED)); + + const UnsupportedContentDescription* offer_unsupported_description = + offer_content->media_description()->as_unsupported(); + std::unique_ptr<MediaContentDescription> unsupported_answer = + std::make_unique<UnsupportedContentDescription>( + offer_unsupported_description->media_type()); + unsupported_answer->set_protocol(offer_unsupported_description->protocol()); + + if (!AddTransportAnswer(media_description_options.mid, + *(unsupported_transport.get()), answer)) { + return false; + } + answer->AddContent(media_description_options.mid, offer_content->type, + /*rejected=*/true, std::move(unsupported_answer)); + return true; +} + +void MediaSessionDescriptionFactory::ComputeAudioCodecsIntersectionAndUnion() { + const webrtc::FieldTrialsView* field_trials = + &transport_desc_factory_->trials(); + audio_sendrecv_codecs_.clear(); + all_audio_codecs_.clear(); + // Compute the audio codecs union. + for (const AudioCodec& send : audio_send_codecs_) { + all_audio_codecs_.push_back(send); + if (!FindMatchingCodec<AudioCodec>(audio_send_codecs_, audio_recv_codecs_, + send, nullptr, field_trials)) { + // It doesn't make sense to have an RTX codec we support sending but not + // receiving. + RTC_DCHECK(!IsRtxCodec(send)); + } + } + for (const AudioCodec& recv : audio_recv_codecs_) { + if (!FindMatchingCodec<AudioCodec>(audio_recv_codecs_, audio_send_codecs_, + recv, nullptr, field_trials)) { + all_audio_codecs_.push_back(recv); + } + } + // Use NegotiateCodecs to merge our codec lists, since the operation is + // essentially the same. Put send_codecs as the offered_codecs, which is the + // order we'd like to follow. The reasoning is that encoding is usually more + // expensive than decoding, and prioritizing a codec in the send list probably + // means it's a codec we can handle efficiently. + NegotiateCodecs(audio_recv_codecs_, audio_send_codecs_, + &audio_sendrecv_codecs_, true, field_trials); +} + +void MediaSessionDescriptionFactory::ComputeVideoCodecsIntersectionAndUnion() { + const webrtc::FieldTrialsView* field_trials = + &transport_desc_factory_->trials(); + video_sendrecv_codecs_.clear(); + + // Use ComputeCodecsUnion to avoid having duplicate payload IDs + all_video_codecs_ = + ComputeCodecsUnion(video_recv_codecs_, video_send_codecs_, field_trials); + + // Use NegotiateCodecs to merge our codec lists, since the operation is + // essentially the same. Put send_codecs as the offered_codecs, which is the + // order we'd like to follow. The reasoning is that encoding is usually more + // expensive than decoding, and prioritizing a codec in the send list probably + // means it's a codec we can handle efficiently. + NegotiateCodecs(video_recv_codecs_, video_send_codecs_, + &video_sendrecv_codecs_, true, field_trials); +} + +bool IsMediaContent(const ContentInfo* content) { + return (content && (content->type == MediaProtocolType::kRtp || + content->type == MediaProtocolType::kSctp)); +} + +bool IsAudioContent(const ContentInfo* content) { + return IsMediaContentOfType(content, MEDIA_TYPE_AUDIO); +} + +bool IsVideoContent(const ContentInfo* content) { + return IsMediaContentOfType(content, MEDIA_TYPE_VIDEO); +} + +bool IsDataContent(const ContentInfo* content) { + return IsMediaContentOfType(content, MEDIA_TYPE_DATA); +} + +bool IsUnsupportedContent(const ContentInfo* content) { + return IsMediaContentOfType(content, MEDIA_TYPE_UNSUPPORTED); +} + +const ContentInfo* GetFirstMediaContent(const ContentInfos& contents, + MediaType media_type) { + for (const ContentInfo& content : contents) { + if (IsMediaContentOfType(&content, media_type)) { + return &content; + } + } + return nullptr; +} + +const ContentInfo* GetFirstAudioContent(const ContentInfos& contents) { + return GetFirstMediaContent(contents, MEDIA_TYPE_AUDIO); +} + +const ContentInfo* GetFirstVideoContent(const ContentInfos& contents) { + return GetFirstMediaContent(contents, MEDIA_TYPE_VIDEO); +} + +const ContentInfo* GetFirstDataContent(const ContentInfos& contents) { + return GetFirstMediaContent(contents, MEDIA_TYPE_DATA); +} + +const ContentInfo* GetFirstMediaContent(const SessionDescription* sdesc, + MediaType media_type) { + if (sdesc == nullptr) { + return nullptr; + } + + return GetFirstMediaContent(sdesc->contents(), media_type); +} + +const ContentInfo* GetFirstAudioContent(const SessionDescription* sdesc) { + return GetFirstMediaContent(sdesc, MEDIA_TYPE_AUDIO); +} + +const ContentInfo* GetFirstVideoContent(const SessionDescription* sdesc) { + return GetFirstMediaContent(sdesc, MEDIA_TYPE_VIDEO); +} + +const ContentInfo* GetFirstDataContent(const SessionDescription* sdesc) { + return GetFirstMediaContent(sdesc, MEDIA_TYPE_DATA); +} + +const MediaContentDescription* GetFirstMediaContentDescription( + const SessionDescription* sdesc, + MediaType media_type) { + const ContentInfo* content = GetFirstMediaContent(sdesc, media_type); + return (content ? content->media_description() : nullptr); +} + +const AudioContentDescription* GetFirstAudioContentDescription( + const SessionDescription* sdesc) { + auto desc = GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_AUDIO); + return desc ? desc->as_audio() : nullptr; +} + +const VideoContentDescription* GetFirstVideoContentDescription( + const SessionDescription* sdesc) { + auto desc = GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_VIDEO); + return desc ? desc->as_video() : nullptr; +} + +const SctpDataContentDescription* GetFirstSctpDataContentDescription( + const SessionDescription* sdesc) { + auto desc = GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_DATA); + return desc ? desc->as_sctp() : nullptr; +} + +// +// Non-const versions of the above functions. +// + +ContentInfo* GetFirstMediaContent(ContentInfos* contents, + MediaType media_type) { + for (ContentInfo& content : *contents) { + if (IsMediaContentOfType(&content, media_type)) { + return &content; + } + } + return nullptr; +} + +ContentInfo* GetFirstAudioContent(ContentInfos* contents) { + return GetFirstMediaContent(contents, MEDIA_TYPE_AUDIO); +} + +ContentInfo* GetFirstVideoContent(ContentInfos* contents) { + return GetFirstMediaContent(contents, MEDIA_TYPE_VIDEO); +} + +ContentInfo* GetFirstDataContent(ContentInfos* contents) { + return GetFirstMediaContent(contents, MEDIA_TYPE_DATA); +} + +ContentInfo* GetFirstMediaContent(SessionDescription* sdesc, + MediaType media_type) { + if (sdesc == nullptr) { + return nullptr; + } + + return GetFirstMediaContent(&sdesc->contents(), media_type); +} + +ContentInfo* GetFirstAudioContent(SessionDescription* sdesc) { + return GetFirstMediaContent(sdesc, MEDIA_TYPE_AUDIO); +} + +ContentInfo* GetFirstVideoContent(SessionDescription* sdesc) { + return GetFirstMediaContent(sdesc, MEDIA_TYPE_VIDEO); +} + +ContentInfo* GetFirstDataContent(SessionDescription* sdesc) { + return GetFirstMediaContent(sdesc, MEDIA_TYPE_DATA); +} + +MediaContentDescription* GetFirstMediaContentDescription( + SessionDescription* sdesc, + MediaType media_type) { + ContentInfo* content = GetFirstMediaContent(sdesc, media_type); + return (content ? content->media_description() : nullptr); +} + +AudioContentDescription* GetFirstAudioContentDescription( + SessionDescription* sdesc) { + auto desc = GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_AUDIO); + return desc ? desc->as_audio() : nullptr; +} + +VideoContentDescription* GetFirstVideoContentDescription( + SessionDescription* sdesc) { + auto desc = GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_VIDEO); + return desc ? desc->as_video() : nullptr; +} + +SctpDataContentDescription* GetFirstSctpDataContentDescription( + SessionDescription* sdesc) { + auto desc = GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_DATA); + return desc ? desc->as_sctp() : nullptr; +} + +} // namespace cricket |