// 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::AUDIO ||
description->type() == MediaType::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 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()) { 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;
}
} 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);
// 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,
PayloadTypeSuggester& suggester,
absl::string_view mid,
RtpTransceiverIdDomain id_domain) {
for (auto reference_extension : reference_extensions) {
if (!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 RtpExtension* existing =
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 { auto suggested_id = suggester.SuggestRtpHeaderExtensionId(
mid, reference_extension, id_domain);
if (suggested_id.ok()) {
reference_extension.id = suggested_id.value();
} else {
RTC_LOG(LS_ERROR)
<< "Failed to suggest RTP header extension ID for "
<< reference_extension.uri << ", error " << suggested_id.error();
}
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 RtpExtension* FindHeaderExtensionByUriDiscardUnsupported( const std::vector<RtpExtension>& extensions,
absl::string_view uri,
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 (!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 == RtpExtension::kRequireEncryptedExtension) { return nullptr;
}
// Instruct to only return non-encrypted extensions
filter = RtpExtension::Filter::kDiscardEncryptedExtension;
}
auto negotiate_extension = [&](const RtpExtension& ours, const RtpExtension* theirs) {
if (theirs && theirs->encrypt == ours.encrypt) { // We MUST use their ID in the answer, and we should also record it // in our suggester to ensure consistency and persistence.
RTCError error =
suggester.AddRtpHeaderExtensionMapping(mid, *theirs, /*local=*/false);
RTC_DCHECK(error.ok());
negotiated_extensions->push_back(*theirs);
}
};
for (const RtpExtension& ours : local_extensions) {
if (ours.uri == RtpExtension::kGenericFrameDescriptorUri00)
frame_descriptor_in_local = true; else if (ours.uri == RtpExtension::kDependencyDescriptorUri)
dependency_descriptor_in_local = true; else if (ours.uri == RtpExtension::kAbsoluteCaptureTimeUri)
abs_capture_time_in_local = true;
// 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 RtpExtension* theirs = FindHeaderExtensionByUriDiscardUnsupported(
offered_extensions, RtpExtension::kDependencyDescriptorUri, filter);
if (theirs) {
RTCError error =
suggester.AddRtpHeaderExtensionMapping(mid, *theirs, /*local=*/false);
RTC_DCHECK(error.ok());
negotiated_extensions->push_back(*theirs);
}
}
if (!frame_descriptor_in_local) { const RtpExtension* theirs = FindHeaderExtensionByUriDiscardUnsupported(
offered_extensions, RtpExtension::kGenericFrameDescriptorUri00, filter);
if (theirs) {
RTCError error =
suggester.AddRtpHeaderExtensionMapping(mid, *theirs, /*local=*/false);
RTC_DCHECK(error.ok());
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 RtpExtension* theirs = FindHeaderExtensionByUriDiscardUnsupported(
offered_extensions, RtpExtension::kAbsoluteCaptureTimeUri, filter);
if (theirs) {
RTCError error =
suggester.AddRtpHeaderExtensionMapping(mid, *theirs, /*local=*/false);
RTC_DCHECK(error.ok());
negotiated_extensions->push_back(*theirs);
}
}
}
// Negotiates Sframe support between the offer and the local answerer options. bool NegotiateSframeUsage( const MediaContentDescription* offer, const MediaDescriptionOptions& media_description_options) { return offer->sframe_enabled() && media_description_options.sframe_enabled;
}
// 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,
PayloadTypeSuggester& suggester,
RtpTransceiverIdDomain id_domain) {
answer->set_extmap_allow_mixed_enum(offer->extmap_allow_mixed_enum()); const RtpExtension::Filter extensions_filter =
enable_encrypted_rtp_header_extensions
? RtpExtension::Filter::kPreferEncryptedExtension
: 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); break;
}
}
}
}
RtpHeaderExtensions negotiated_rtp_extensions;
NegotiateRtpHeaderExtensions(
local_rtp_extensions_to_reply_with, offer->rtp_header_extensions(),
extensions_filter, &negotiated_rtp_extensions, suggester,
media_description_options.mid, id_domain);
answer->set_rtp_header_extensions(negotiated_rtp_extensions);
bool IsMediaProtocolSupported(MediaType type,
absl::string_view 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 == MediaType::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 = nullptr;
if (current_description) { const TransportInfo* info =
current_description->GetTransportInfoByName(content_name);
if (info) {
desc = &info->description;
}
} return desc;
}
RTCErrorOr<std::unique_ptr<SessionDescription>>
MediaSessionDescriptionFactory::CreateOfferOrError( const MediaSessionOptions& session_options, const SessionDescription* current_description) const {
RTC_DCHECK_DISALLOW_THREAD_BLOCKING_CALLS(); // 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 MediaType::AUDIO: case MediaType::VIDEO:
error = AddRtpContentForOffer(
media_description_options, session_options, current_content,
current_description,
media_description_options.type == MediaType::AUDIO
? extensions_with_ids.audio
: extensions_with_ids.video,
¤t_streams, offer.get(), &ice_credentials); break; case MediaType::DATA:
error = AddDataContentForOffer(media_description_options,
session_options, current_content,
current_description, ¤t_streams,
offer.get(), &ice_credentials); break; case MediaType::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.mid());
}
if (!offer_bundle.content_names().empty()) {
offer->AddGroup(offer_bundle);
if (!UpdateTransportInfoForBundle(offer_bundle, offer.get())) { return LOG_ERROR(
RTCError(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(kMsidSignalingSemantic |
kMsidSignalingMediaSection |
kMsidSignalingSsrcAttribute);
} else { // Plan B always signals MSID using a=ssrc lines.
offer->set_msid_signaling(kMsidSignalingSemantic |
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());
// Decide what congestion control feedback format we're using. bool has_ack_ccfb = false;
if (accept_offer_with_rfc_8888_) { bool all_rtp_have_ccfb = true; bool any_rtp_has_ccfb = false;
for (constauto& content : offer->contents()) {
if (content.type != MediaProtocolType::kRtp) { continue;
}
if (content.media_description()->rtcp_fb_ack_ccfb()) {
any_rtp_has_ccfb = true;
} else {
all_rtp_have_ccfb = false;
}
}
if (any_rtp_has_ccfb) {
if (all_rtp_have_ccfb) {
has_ack_ccfb = true;
} else {
RTC_LOG(LS_ERROR)
<< "Inconsistent rtcp_fb_ack_ccfb marking, ignoring all";
}
}
}
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->mid()); // 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];
} // Don't offer the transport-cc header extension if "ack ccfb" is in use. auto header_extensions_in = media_description_options.header_extensions;
if (has_ack_ccfb) {
for (auto& option : header_extensions_in) {
if (option.uri == RtpExtension::kTransportSequenceNumberUri) {
option.direction = RtpTransceiverDirection::kStopped;
}
}
}
RtpHeaderExtensions header_extensions = RtpHeaderExtensionsFromCapabilities(
UnstoppedRtpHeaderExtensionCapabilities(header_extensions_in));
RTCError error; switch (media_description_options.type) { case MediaType::AUDIO: case MediaType::VIDEO:
error = AddRtpContentForAnswer(
media_description_options, session_options, offer_content, offer,
current_content, current_description, bundle_transport,
header_extensions, ¤t_streams, answer.get(), has_ack_ccfb,
&ice_credentials); break; case MediaType::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 MediaType::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.mid());
answer_bundles[bundle_index.value()].AddContentName(added.mid());
bundle_transports[bundle_index.value()].reset(
new TransportInfo(*answer->GetTransportInfoByName(added.mid())));
}
}
// 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())) { return LOG_ERROR(
RTCError(RTCErrorType::INTERNAL_ERROR)
<< "CreateAnswer 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_) { // Unified Plan needs to look at what the offer included to find the most // compatible answer.
int msid_signaling = offer->msid_signaling();
if (msid_signaling == (kMsidSignalingSemantic | kMsidSignalingMediaSection |
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(kMsidSignalingSemantic |
kMsidSignalingMediaSection);
} else if (msid_signaling ==
(kMsidSignalingSemantic | kMsidSignalingSsrcAttribute) ||
msid_signaling == kMsidSignalingSsrcAttribute) { // If only a=ssrc MSID signaling method was used, we're probably talking // to a Plan B endpoint so respond with just a=ssrc MSID.
answer->set_msid_signaling(kMsidSignalingSemantic |
kMsidSignalingSsrcAttribute);
} else { // 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 recvonly or inactive from the remote point of // view. // We don't have any information to say whether the endpoint is Plan B // or Unified Plan. Since plan-b is obsolete, do not respond with it. // We assume that endpoints not supporting MSID will silently ignore // the a=msid lines they do not understand.
answer->set_msid_signaling(kMsidSignalingSemantic |
kMsidSignalingMediaSection);
}
} else { // Plan B always signals MSID using a=ssrc lines.
answer->set_msid_signaling(kMsidSignalingSemantic |
kMsidSignalingSsrcAttribute);
}
return answer;
}
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.
RtpTransceiverIdDomain id_domain =
extmap_allow_mixed ? RtpTransceiverIdDomain::kTwoByteAllowed
: RtpTransceiverIdDomain::kOneByteOnly;
RtpHeaderExtensions all_encountered_extensions;
AudioVideoRtpHeaderExtensions offered_extensions; // First - get all extensions from the current description if the media type // is used. // Add them to the suggester so the local ids are not reused if a new media // type is added.
RTC_DCHECK(codec_lookup_helper_->PayloadTypeSuggester());
for (const ContentInfo* content : current_active_contents) {
if (IsMediaContentOfType(content, MediaType::AUDIO)) {
MergeRtpHdrExts(content->media_description()->rtp_header_extensions(),
enable_encrypted_rtp_header_extensions_,
&offered_extensions.audio, &all_encountered_extensions,
*codec_lookup_helper_->PayloadTypeSuggester(),
content->mid(), id_domain);
} else if (IsMediaContentOfType(content, MediaType::VIDEO)) {
MergeRtpHdrExts(content->media_description()->rtp_header_extensions(),
enable_encrypted_rtp_header_extensions_,
&offered_extensions.video, &all_encountered_extensions,
*codec_lookup_helper_->PayloadTypeSuggester(),
content->mid(), id_domain);
}
}
// Add all encountered header extensions in the media description options that // are not in the current description.
RTC_DCHECK(codec_lookup_helper_->PayloadTypeSuggester());
for (constauto& entry : media_description_options) {
RtpHeaderExtensions filtered_extensions =
filtered_rtp_header_extensions(UnstoppedOrPresentRtpHeaderExtensions(
entry.header_extensions, all_encountered_extensions));
if (entry.type == MediaType::AUDIO)
MergeRtpHdrExts(
filtered_extensions, enable_encrypted_rtp_header_extensions_,
&offered_extensions.audio, &all_encountered_extensions,
*codec_lookup_helper_->PayloadTypeSuggester(), entry.mid, id_domain); else if (entry.type == MediaType::VIDEO)
MergeRtpHdrExts(
filtered_extensions, enable_encrypted_rtp_header_extensions_,
&offered_extensions.video, &all_encountered_extensions,
*codec_lookup_helper_->PayloadTypeSuggester(), entry.mid, id_domain);
} return offered_extensions;
}
// Add the RTP description to the SessionDescription. // If media_description_options.codecs_to_include is set, those codecs are used. // // If it is not set, the codecs used are computed based on: // `codecs` = set of all possible codecs that can be used, with correct // payload type mappings // // `supported_codecs` = set of codecs that are supported for the direction // of this m= section // `current_content` = current description, may be null. // current_content->codecs() = set of previously negotiated codecs for this m= // section // // The payload types should come from codecs, but the order should come // from current_content->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.
RTCError MediaSessionDescriptionFactory::AddRtpContentForOffer( const MediaDescriptionOptions& media_description_options, const MediaSessionOptions& session_options, const ContentInfo* current_content, const SessionDescription* current_description, const RtpHeaderExtensions& header_extensions,
StreamParamsVec* current_streams,
SessionDescription* session_description,
IceCredentialsIterator* ice_credentials) const {
RTC_DCHECK(media_description_options.type == MediaType::AUDIO ||
media_description_options.type == MediaType::VIDEO);
// Insecure transport should only occur in testing. bool secure_transport = !(transport_desc_factory_->insecure());
SetMediaProtocol(secure_transport, content_description.get());
std::vector<std::string> crypto_suites; // 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 (session_options.use_sctp_snap) {
if (current_content && current_content->media_description()) { auto current_data_description =
current_content->media_description()->as_sctp();
RTC_DCHECK(current_data_description);
data->set_sctp_init(current_data_description->sctp_init());
} else {
data->set_sctp_init(sctp_factory_->GenerateConnectionToken(env_));
}
}
auto error = CreateContentOffer(media_description_options, session_options,
RtpHeaderExtensions(), ssrc_generator(),
current_streams, data.get());
if (!error.ok()) { return error;
}
// `codecs` = set of all possible codecs that can be used, with correct // payload type mappings // // `supported_codecs` = set of codecs that are supported for the direction // of this m= section // // mcd->codecs() = set of previously negotiated codecs for this m= section // // The payload types should come from codecs, but the order should come // from mcd->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.
RTCError MediaSessionDescriptionFactory::AddRtpContentForAnswer( 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 RtpHeaderExtensions& header_extensions,
StreamParamsVec* current_streams,
SessionDescription* answer, bool include_ccfb_in_answer,
IceCredentialsIterator* ice_credentials) const {
RTC_DCHECK(media_description_options.type == MediaType::AUDIO ||
media_description_options.type == MediaType::VIDEO);
RTC_CHECK(
IsMediaContentOfType(offer_content, media_description_options.type)); const RtpMediaContentDescription* offer_content_description;
if (media_description_options.type == MediaType::AUDIO) {
offer_content_description = offer_content->media_description()->as_audio();
} else {
offer_content_description = offer_content->media_description()->as_video();
} // If this section is part of a bundle, bundle_transport is non-null. // Then require_transport_attributes is false - we can handle sections // without the DTLS parameters. For rejected m-lines it does not matter. // Otherwise, transport attributes MUST be present.
std::unique_ptr<TransportDescription> transport = CreateTransportAnswer(
media_description_options.mid, offer_description,
media_description_options.transport_options, current_description,
!offer_content->rejected && bundle_transport == nullptr, ice_credentials);
if (!transport) { return LOG_ERROR(
RTCError(RTCErrorType::INTERNAL_ERROR)
<< "Failed to create transport answer, transport is missing");
}
// 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_content_description->direction(); auto answer_rtd = NegotiateRtpTransceiverDirection(offer_rtd, wants_rtd);
codecs_to_include = error_or_filtered_codecs.MoveValue(); // Determine if we have media codecs in common. bool has_usable_media_codecs =
std::find_if(codecs_to_include.begin(), codecs_to_include.end(),
[](const Codec& c) { return c.IsMediaCodec() && !IsComfortNoiseCodec(c);
}) != codecs_to_include.end();
bool bundle_enabled = offer_description->HasGroup(GROUP_TYPE_BUNDLE) &&
session_options.bundle_enabled;
std::unique_ptr<MediaContentDescription> answer_content;
if (media_description_options.type == MediaType::AUDIO) {
answer_content = std::make_unique<AudioContentDescription>();
} else {
answer_content = std::make_unique<VideoContentDescription>();
} // RFC 8888 support. Only answer with "ack ccfb" if offer has it and // experiment is enabled.
if (offer_content_description->rtcp_fb_ack_ccfb()) {
if (accept_offer_with_rfc_8888_ && include_ccfb_in_answer) {
answer_content->set_rtcp_fb_ack_ccfb(true);
for (auto& codec : codecs_to_include) {
codec.feedback_params.Remove(FeedbackParam(kRtcpFbParamTransportCc));
}
}
}
if (!SetCodecsInAnswer(offer_content_description, codecs_to_include,
media_description_options, session_options,
ssrc_generator(), current_streams,
answer_content.get(),
transport_desc_factory_->trials())) { return LOG_ERROR(RTCError(RTCErrorType::INTERNAL_ERROR)
<< "Failed to set codecs in answer");
}
RTC_DCHECK(codec_lookup_helper_->PayloadTypeSuggester());
if (!CreateMediaContentAnswer(
offer_content_description, media_description_options, session_options,
filtered_rtp_header_extensions(header_extensions), ssrc_generator(),
enable_encrypted_rtp_header_extensions_, current_streams,
bundle_enabled, answer_content.get(),
*codec_lookup_helper_->PayloadTypeSuggester(),
offer_description->extmap_allow_mixed()
? RtpTransceiverIdDomain::kTwoByteAllowed
: RtpTransceiverIdDomain::kOneByteOnly)) { return LOG_ERROR(RTCError(RTCErrorType::INTERNAL_ERROR)
<< "Failed to create answer");
}
bool bundle_enabled = offer_description->HasGroup(GROUP_TYPE_BUNDLE) &&
session_options.bundle_enabled;
RTC_CHECK(IsMediaContentOfType(offer_content, MediaType::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));
}
RTC_DCHECK(codec_lookup_helper_->PayloadTypeSuggester());
if (!CreateMediaContentAnswer(
offer_data_description, media_description_options, session_options,
RtpHeaderExtensions(), ssrc_generator(),
enable_encrypted_rtp_header_extensions_, current_streams,
bundle_enabled, data_answer.get(),
*codec_lookup_helper_->PayloadTypeSuggester(),
offer_description->extmap_allow_mixed()
? RtpTransceiverIdDomain::kTwoByteAllowed
: RtpTransceiverIdDomain::kOneByteOnly)) { return LOG_ERROR(RTCError(RTCErrorType::INTERNAL_ERROR)
<< "Failed to create answer");
} // 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);
// Respond with sctp-init if the offer had sctp-init.
if (session_options.use_sctp_snap) {
if (offer_data_description->sctp_init().has_value()) {
if (current_content && current_content->media_description()) { auto current_data_description =
current_content->media_description()->as_sctp();
RTC_DCHECK(current_data_description);
data_answer->as_sctp()->set_sctp_init(
current_data_description->sctp_init());
} else {
data_answer->as_sctp()->set_sctp_init(
sctp_factory_->GenerateConnectionToken(env_));
}
}
}
} else {
RTC_DCHECK_NOTREACHED() << "Non-SCTP data content found";
}
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.