/* * 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.
*/
// Finds all StreamParams of all media types and attach them to stream_params.
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;
}
// 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";
}
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. 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. bool AddStreamParams(const std::vector<SenderOptions>& sender_options, const std::string& rtcp_cname,
UniqueRandomIdGenerator* ssrc_generator,
StreamParamsVec* current_streams,
MediaContentDescription* content_description, const webrtc::FieldTrialsView& field_trials) { // SCTP streams are not negotiated using SDP/ContentDescriptions. if (IsSctpProtocol(content_description->protocol())) { returntrue;
}
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);
}
} returntrue;
}
// 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`. bool UpdateTransportInfoForBundle(const ContentGroup& bundle_group,
SessionDescription* sdesc) { // The bundle should not be empty. if (!sdesc || !bundle_group.FirstContentName()) { returnfalse;
}
// 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) { returnfalse;
}
// 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;
}
} returntrue;
}
// 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.
RTCError CreateContentOffer( const MediaDescriptionOptions& media_description_options, const MediaSessionOptions& session_options, const RtpHeaderExtensions& rtp_extensions,
UniqueRandomIdGenerator* ssrc_generator,
StreamParamsVec* current_streams,
MediaContentDescription* offer) {
offer->set_rtcp_mux(session_options.rtcp_mux_enabled);
offer->set_rtcp_reduced_size(true);
// Build the vector of header extensions with directions for this // media_description's options.
RtpHeaderExtensions extensions; for (constauto& extension_with_id : rtp_extensions) { for (constauto& extension : media_description_options.header_extensions) { if (extension_with_id.uri == extension.uri &&
extension_with_id.encrypt == extension.preferred_encrypt) { // TODO(crbug.com/1051821): Configure the extension direction from // the information in the media_description_options extension // capability. if (extension.direction != RtpTransceiverDirection::kStopped) {
extensions.push_back(extension_with_id);
}
}
}
}
offer->set_rtp_header_extensions(extensions);
// Update the ID fields of the codec vector. // If any codec has an ID with value "kIdNotSet", use the payload type suggester // to assign and record a payload type for it. // If there is a RED codec without its fmtp parameter, give it the ID of the // first OPUS codec in the codec list.
webrtc::RTCError AssignCodecIdsAndLinkRed(
webrtc::PayloadTypeSuggester* pt_suggester, const std::string& mid,
std::vector<Codec>& codecs) { int opus_codec = Codec::kIdNotSet; for (cricket::Codec& codec : codecs) { if (codec.id == Codec::kIdNotSet) { // Add payload types to codecs, if needed // This should only happen if WebRTC-PayloadTypesInTransport field trial // is enabled.
RTC_CHECK(pt_suggester); auto result = pt_suggester->SuggestPayloadType(mid, codec); if (!result.ok()) { return result.error();
}
codec.id = result.value();
} // record first Opus codec id if (absl::EqualsIgnoreCase(codec.name, kOpusCodecName) &&
opus_codec == Codec::kIdNotSet) {
opus_codec = codec.id;
}
} if (opus_codec != Codec::kIdNotSet) { for (cricket::Codec& codec : codecs) { if (codec.type == Codec::Type::kAudio &&
absl::EqualsIgnoreCase(codec.name, kRedCodecName)) { if (codec.params.empty()) { char buffer[100];
rtc::SimpleStringBuilder param(buffer);
param << opus_codec << "/" << opus_codec;
RTC_LOG(LS_ERROR) << "DEBUG: Setting RED param to " << param.str();
codec.SetParam(kCodecParamNotInNameValueFormat, param.str());
}
}
}
} return webrtc::RTCError::OK();
}
void NegotiateCodecs(const std::vector<Codec>& local_codecs, const std::vector<Codec>& offered_codecs,
std::vector<Codec>* negotiated_codecs, bool keep_offer_order) { for (const Codec& ours : local_codecs) {
std::optional<Codec> theirs =
webrtc::FindMatchingCodec(local_codecs, offered_codecs, ours); // Note that we intentionally only find one matching codec for each of our // local codecs, in case the remote offer contains duplicate codecs. if (theirs) {
Codec negotiated = ours;
NegotiatePacketization(ours, *theirs, &negotiated);
negotiated.IntersectFeedbackParams(*theirs); if (negotiated.GetResiliencyType() == Codec::ResiliencyType::kRtx) { constauto apt_it =
theirs->params.find(kCodecParamAssociatedPayloadType); // webrtc::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. constauto rtx_time_it = theirs->params.find(kCodecParamRtxTime); if (rtx_time_it != theirs->params.end()) {
negotiated.SetParam(kCodecParamRtxTime, rtx_time_it->second);
}
} elseif (negotiated.GetResiliencyType() ==
Codec::ResiliencyType::kRed) { constauto 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);
} #ifdef RTC_ENABLE_H265 if (absl::EqualsIgnoreCase(ours.name, kH265CodecName)) {
webrtc::H265GenerateProfileTierLevelForAnswer(
ours.params, theirs->params, &negotiated.params);
NegotiateTxMode(ours, *theirs, &negotiated);
} #endif
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 Codec& codec : offered_codecs) {
payload_type_preferences[codec.id] = preference--;
}
absl::c_sort(*negotiated_codecs, [&payload_type_preferences]( const Codec& a, const Codec& b) { return payload_type_preferences[a.id] > payload_type_preferences[b.id];
});
}
}
// Find the codec in `codec_list` that `rtx_codec` is associated with. const Codec* GetAssociatedCodecForRtx(const std::vector<Codec>& codec_list, const Codec& rtx_codec) {
std::string associated_pt_str; if (!rtx_codec.GetParam(kCodecParamAssociatedPayloadType,
&associated_pt_str)) {
RTC_LOG(LS_WARNING) << "RTX codec " << rtx_codec.id
<< " 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.id
<< " to an integer."; return nullptr;
}
// Find the associated codec for the RTX codec. const Codec* 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.id
<< ".";
} return associated_codec;
}
// Find the codec in `codec_list` that `red_codec` is associated with. const Codec* GetAssociatedCodecForRed(const std::vector<Codec>& codec_list, const Codec& red_codec) {
std::string fmtp; if (!red_codec.GetParam(kCodecParamNotInNameValueFormat, &fmtp)) { // Don't log for video/RED where this is normal. if (red_codec.type == Codec::Type::kAudio) {
RTC_LOG(LS_WARNING) << "RED codec " << red_codec.id
<< " is missing an associated payload type.";
} 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.id
<< " to an integer."; return nullptr;
}
// Find the associated codec for the RED codec. const Codec* 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.id
<< ".";
} 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. void MergeCodecs(const std::vector<Codec>& reference_codecs,
std::vector<Codec>* offered_codecs,
UsedPayloadTypes* used_pltypes) { // 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 Codec& reference_codec : reference_codecs) { if (reference_codec.GetResiliencyType() != Codec::ResiliencyType::kRtx &&
reference_codec.GetResiliencyType() != Codec::ResiliencyType::kRed &&
!webrtc::FindMatchingCodec(reference_codecs, *offered_codecs,
reference_codec)) {
Codec codec = reference_codec;
used_pltypes->FindAndSetIdUsed(&codec);
offered_codecs->push_back(codec);
}
}
// Add all new RTX or RED codecs. for (const Codec& reference_codec : reference_codecs) { if (reference_codec.GetResiliencyType() == Codec::ResiliencyType::kRtx &&
!webrtc::FindMatchingCodec(reference_codecs, *offered_codecs,
reference_codec)) {
Codec rtx_codec = reference_codec; const Codec* 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.
std::optional<Codec> matching_codec = webrtc::FindMatchingCodec(
reference_codecs, *offered_codecs, *associated_codec); if (!matching_codec) {
RTC_LOG(LS_WARNING)
<< "Couldn't find matching " << associated_codec->name << " codec."; continue;
}
// `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.
std::vector<Codec> MatchCodecPreference( const std::vector<webrtc::RtpCodecCapability>& codec_preferences, const std::vector<Codec>& codecs, const std::vector<Codec>& supported_codecs) {
std::vector<Codec> filtered_codecs; bool want_rtx = false; bool want_red = false;
if (found_codec != supported_codecs.end()) {
std::optional<Codec> found_codec_with_correct_pt =
webrtc::FindMatchingCodec(supported_codecs, codecs, *found_codec); if (found_codec_with_correct_pt) { // RED may already have been added if its primary codec is before RED // in the codec list. bool is_red_codec = found_codec_with_correct_pt->GetResiliencyType() ==
Codec::ResiliencyType::kRed; if (!is_red_codec || !red_was_added) {
filtered_codecs.push_back(*found_codec_with_correct_pt);
red_was_added = is_red_codec ? true : red_was_added;
}
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 (constauto& codec : codecs) { if (codec.GetResiliencyType() == Codec::ResiliencyType::kRtx) { constauto apt =
codec.params.find(cricket::kCodecParamAssociatedPayloadType); if (apt != codec.params.end() && apt->second == id) {
filtered_codecs.push_back(codec); break;
}
} elseif (codec.GetResiliencyType() ==
Codec::ResiliencyType::kRed) { // 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. constauto 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.empty() &&
redundant_payloads[0] == id) { if (!red_was_added) {
filtered_codecs.push_back(codec);
red_was_added = true;
} break;
}
}
}
}
}
}
}
}
return filtered_codecs;
}
// Compute the union of `codecs1` and `codecs2`.
std::vector<Codec> ComputeCodecsUnion(const std::vector<Codec>& codecs1, const std::vector<Codec>& codecs2) {
std::vector<Codec> all_codecs;
UsedPayloadTypes used_payload_types; for (const Codec& codec : codecs1) {
Codec 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(codecs2, &all_codecs, &used_payload_types);
return all_codecs;
}
// Adds all extensions from `reference_extensions` to `offered_extensions` that // don't already exist in `offered_extensions` and ensures the IDs don't // collide. If an extension is added, it's also added to // `all_encountered_extensions`. Also when doing the addition a new ID is set // for that extension. `offered_extensions` is for either audio or video while // `all_encountered_extensions` is used for both audio and video. There could be // overlap between audio extensions and video extensions. void MergeRtpHdrExts(const RtpHeaderExtensions& reference_extensions, bool enable_encrypted_rtp_header_extensions,
RtpHeaderExtensions* offered_extensions,
RtpHeaderExtensions* all_encountered_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 &&
!enable_encrypted_rtp_header_extensions) { // Negotiating of encrypted headers is deactivated. continue;
} const webrtc::RtpExtension* existing =
webrtc::RtpExtension::FindHeaderExtensionByUriAndEncryption(
*all_encountered_extensions, reference_extension.uri,
reference_extension.encrypt); if (existing) { // E.g. in the case where the same RTP header extension is used for // audio and video.
offered_extensions->push_back(*existing);
} else {
used_ids->FindAndSetIdUsed(&reference_extension);
all_encountered_extensions->push_back(reference_extension);
offered_extensions->push_back(reference_extension);
}
}
}
}
// Mostly identical to RtpExtension::FindHeaderExtensionByUri but discards any // encrypted extensions that this implementation cannot encrypt. 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;
}
const webrtc::RtpExtension* theirs =
FindHeaderExtensionByUriDiscardUnsupported(offered_extensions, ours.uri,
filter); if (theirs && theirs->encrypt == ours.encrypt) { // We respond with their RTP header extension id.
negotiated_extensions->push_back(*theirs);
}
}
// 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);
}
}
}
// 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. bool CreateMediaContentAnswer( const MediaContentDescription* offer, const MediaDescriptionOptions& media_description_options, const MediaSessionOptions& session_options, 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;
// Filter local extensions by capabilities and direction.
RtpHeaderExtensions local_rtp_extensions_to_reply_with; for (constauto& extension_with_id : local_rtp_extensions) { for (constauto& extension : media_description_options.header_extensions) { if (extension_with_id.uri == extension.uri &&
extension_with_id.encrypt == extension.preferred_encrypt) { // TODO(crbug.com/1051821): Configure the extension direction from // the information in the media_description_options extension // capability. For now, do not include stopped extensions. // See also crbug.com/webrtc/7477 about the general lack of direction. if (extension.direction != RtpTransceiverDirection::kStopped) {
local_rtp_extensions_to_reply_with.push_back(extension_with_id);
}
}
}
}
RtpHeaderExtensions negotiated_rtp_extensions;
NegotiateRtpHeaderExtensions(local_rtp_extensions_to_reply_with,
offer->rtp_header_extensions(),
extensions_filter, &negotiated_rtp_extensions);
answer->set_rtp_header_extensions(negotiated_rtp_extensions);
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()) { returntrue;
}
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);
}
}
// Gets the TransportInfo of the given `content_name` from the // `current_description`. If doesn't exist, returns a new one. 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;
}
webrtc::RTCErrorOr<Codecs> GetNegotiatedCodecsForOffer( const MediaDescriptionOptions& media_description_options, const MediaSessionOptions& session_options, const ContentInfo* current_content, const std::vector<Codec>& codecs, const std::vector<Codec>& supported_codecs) {
std::vector<Codec> 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, codecs, supported_codecs);
} 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) { if (!IsMediaContentOfType(current_content,
media_description_options.type)) { // Can happen if the remote side re-uses a MID while recycling.
LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR, "Media type for content with mid='" +
current_content->name + "' does not match previous type.");
} const MediaContentDescription* mcd = current_content->media_description(); for (const Codec& codec : mcd->codecs()) { if (webrtc::FindMatchingCodec(mcd->codecs(), codecs, codec)) {
filtered_codecs.push_back(codec);
}
}
} // Add other supported codecs. for (const Codec& codec : supported_codecs) {
std::optional<Codec> found_codec =
webrtc::FindMatchingCodec(supported_codecs, codecs, codec); if (found_codec && !webrtc::FindMatchingCodec(supported_codecs,
filtered_codecs, codec)) { // Use the `found_codec` from `codecs` because it has the // correctly mapped payload type. // This is only done for video since we do not yet have rtx for audio. if (media_description_options.type == MEDIA_TYPE_VIDEO &&
found_codec->GetResiliencyType() == Codec::ResiliencyType::kRtx) { // 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_codecs, codec);
RTC_DCHECK(referenced_codec);
// Find the codec we should be referencing and point to it.
std::optional<Codec> changed_referenced_codec =
webrtc::FindMatchingCodec(supported_codecs, filtered_codecs,
*referenced_codec); if (changed_referenced_codec) {
found_codec->SetParam(kCodecParamAssociatedPayloadType,
changed_referenced_codec->id);
}
}
filtered_codecs.push_back(*found_codec);
}
}
}
if (media_description_options.type == MEDIA_TYPE_AUDIO &&
!session_options.vad_enabled) { // If application doesn't want CN codecs in offer.
StripCNCodecs(&filtered_codecs);
} elseif (media_description_options.type == MEDIA_TYPE_VIDEO &&
session_options.raw_packetization_for_video) { for (Codec& codec : filtered_codecs) { if (codec.IsMediaCodec()) {
codec.packetization = kPacketizationParamRaw;
}
}
} return filtered_codecs;
}
if (!media_description_options.codec_preferences.empty()) {
filtered_codecs = MatchCodecPreference(
media_description_options.codec_preferences, codecs, supported_codecs);
} 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) { if (!IsMediaContentOfType(current_content,
media_description_options.type)) { // Can happen if the remote side re-uses a MID while recycling.
LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR, "Media type for content with mid='" +
current_content->name + "' does not match previous type.");
} const MediaContentDescription* mcd = current_content->media_description(); for (const Codec& codec : mcd->codecs()) { if (webrtc::FindMatchingCodec(mcd->codecs(), codecs, codec)) {
filtered_codecs.push_back(codec);
}
}
} // Add other supported codecs.
std::vector<Codec> other_codecs; for (const Codec& codec : supported_codecs) { if (webrtc::FindMatchingCodec(supported_codecs, codecs, codec) &&
!webrtc::FindMatchingCodec(supported_codecs, filtered_codecs,
codec)) { // We should use the local codec with local parameters and the codec id // would be correctly mapped in `NegotiateCodecs`.
other_codecs.push_back(codec);
}
}
// Use ComputeCodecsUnion to avoid having duplicate payload IDs. // This is a no-op for audio until RTX is added.
filtered_codecs = ComputeCodecsUnion(filtered_codecs, other_codecs);
}
if (media_description_options.type == MEDIA_TYPE_AUDIO &&
!session_options.vad_enabled) { // If application doesn't want CN codecs in offer.
StripCNCodecs(&filtered_codecs);
} elseif (media_description_options.type == MEDIA_TYPE_VIDEO &&
session_options.raw_packetization_for_video) { for (Codec& codec : filtered_codecs) { if (codec.IsMediaCodec()) {
codec.packetization = kPacketizationParamRaw;
}
}
} return filtered_codecs;
}
webrtc::RTCErrorOr<std::unique_ptr<SessionDescription>>
MediaSessionDescriptionFactory::CreateOfferOrError( 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());
}
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.
}
RTCError error; switch (media_description_options.type) { case MEDIA_TYPE_AUDIO: case MEDIA_TYPE_VIDEO:
error = AddRtpContentForOffer(
media_description_options, session_options, current_content,
current_description,
media_description_options.type == MEDIA_TYPE_AUDIO
? extensions_with_ids.audio
: extensions_with_ids.video,
media_description_options.type == MEDIA_TYPE_AUDIO
? offer_audio_codecs
: offer_video_codecs,
¤t_streams, offer.get(), &ice_credentials); break; case MEDIA_TYPE_DATA:
error = AddDataContentForOffer(media_description_options,
session_options, current_content,
current_description, ¤t_streams,
offer.get(), &ice_credentials); break; case MEDIA_TYPE_UNSUPPORTED:
error = AddUnsupportedContentForOffer(
media_description_options, session_options, current_content,
current_description, offer.get(), &ice_credentials); break; default:
RTC_DCHECK_NOTREACHED();
} if (!error.ok()) { return error;
}
++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())) {
LOG_AND_RETURN_ERROR(
RTCErrorType::INTERNAL_ERROR, "CreateOffer failed to UpdateTransportInfoForBundle");
}
}
}
// 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::kMsidSignalingSemantic |
cricket::kMsidSignalingMediaSection |
cricket::kMsidSignalingSsrcAttribute);
} else { // Plan B always signals MSID using a=ssrc lines.
offer->set_msid_signaling(cricket::kMsidSignalingSemantic |
cricket::kMsidSignalingSsrcAttribute);
}
// Must have options for exactly as many sections as in the offer.
RTC_DCHECK_EQ(offer->contents().size(),
session_options.media_description_options.size());
// 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.
Codecs answer_audio_codecs;
Codecs 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);
}
// 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.
std::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));
RTCError error; switch (media_description_options.type) { case MEDIA_TYPE_AUDIO: case MEDIA_TYPE_VIDEO:
error = AddRtpContentForAnswer(
media_description_options, session_options, offer_content, offer,
current_content, current_description, bundle_transport,
media_description_options.type == MEDIA_TYPE_AUDIO
? answer_audio_codecs
: answer_video_codecs,
header_extensions, ¤t_streams, answer.get(),
&ice_credentials); break; case MEDIA_TYPE_DATA:
error = AddDataContentForAnswer(
media_description_options, session_options, offer_content, offer,
current_content, current_description, bundle_transport,
¤t_streams, answer.get(), &ice_credentials); break; case MEDIA_TYPE_UNSUPPORTED:
error = AddUnsupportedContentForAnswer(
media_description_options, session_options, offer_content, offer,
current_content, current_description, bundle_transport,
answer.get(), &ice_credentials); break; default:
RTC_DCHECK_NOTREACHED();
} if (!error.ok()) { return error;
}
++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())) {
LOG_AND_RETURN_ERROR(
RTCErrorType::INTERNAL_ERROR, "CreateAnswer failed to UpdateTransportInfoForBundle.");
}
}
}
}
// The following determines how to signal MSIDs to ensure compatibility with
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5
¤ Dauer der Verarbeitung: 0.72 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.