2020-09-28 13:02:07 +00:00
|
|
|
/*
|
|
|
|
|
* Copyright 2020 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/sdp_offer_answer.h"
|
|
|
|
|
|
2020-10-01 10:23:33 +00:00
|
|
|
#include <algorithm>
|
2022-02-09 12:42:27 +00:00
|
|
|
#include <cstddef>
|
2024-06-04 21:29:14 +00:00
|
|
|
#include <cstdint>
|
|
|
|
|
#include <functional>
|
2020-10-18 16:51:47 +00:00
|
|
|
#include <iterator>
|
2020-10-19 06:35:55 +00:00
|
|
|
#include <map>
|
2021-08-02 10:56:33 +02:00
|
|
|
#include <memory>
|
2024-08-29 13:00:40 +00:00
|
|
|
#include <optional>
|
2020-10-01 10:23:33 +00:00
|
|
|
#include <queue>
|
2024-06-04 21:29:14 +00:00
|
|
|
#include <set>
|
2022-02-09 12:42:27 +00:00
|
|
|
#include <string>
|
2020-10-23 13:30:46 +00:00
|
|
|
#include <utility>
|
2024-06-04 21:29:14 +00:00
|
|
|
#include <vector>
|
2020-10-01 10:23:33 +00:00
|
|
|
|
2020-10-18 16:51:47 +00:00
|
|
|
#include "absl/algorithm/container.h"
|
2020-10-27 07:10:43 +00:00
|
|
|
#include "absl/memory/memory.h"
|
2022-01-17 19:19:56 +01:00
|
|
|
#include "absl/strings/match.h"
|
2020-10-18 16:51:47 +00:00
|
|
|
#include "absl/strings/string_view.h"
|
|
|
|
|
#include "api/array_view.h"
|
2024-06-04 21:29:14 +00:00
|
|
|
#include "api/candidate.h"
|
2020-10-18 16:51:47 +00:00
|
|
|
#include "api/crypto/crypto_options.h"
|
2024-06-04 21:29:14 +00:00
|
|
|
#include "api/jsep.h"
|
|
|
|
|
#include "api/jsep_ice_candidate.h"
|
|
|
|
|
#include "api/make_ref_counted.h"
|
|
|
|
|
#include "api/media_stream_interface.h"
|
|
|
|
|
#include "api/media_types.h"
|
|
|
|
|
#include "api/peer_connection_interface.h"
|
|
|
|
|
#include "api/rtc_error.h"
|
2020-10-18 16:51:47 +00:00
|
|
|
#include "api/rtp_parameters.h"
|
|
|
|
|
#include "api/rtp_receiver_interface.h"
|
|
|
|
|
#include "api/rtp_sender_interface.h"
|
2024-06-04 21:29:14 +00:00
|
|
|
#include "api/rtp_transceiver_direction.h"
|
|
|
|
|
#include "api/rtp_transceiver_interface.h"
|
|
|
|
|
#include "api/scoped_refptr.h"
|
|
|
|
|
#include "api/sequence_checker.h"
|
|
|
|
|
#include "api/set_local_description_observer_interface.h"
|
|
|
|
|
#include "api/set_remote_description_observer_interface.h"
|
|
|
|
|
#include "api/uma_metrics.h"
|
2020-10-22 10:39:40 +00:00
|
|
|
#include "api/video/builtin_video_bitrate_allocator_factory.h"
|
2024-06-04 21:29:14 +00:00
|
|
|
#include "api/video/video_codec_constants.h"
|
2024-09-16 20:28:14 +00:00
|
|
|
#include "call/payload_type.h"
|
2020-10-18 16:51:47 +00:00
|
|
|
#include "media/base/codec.h"
|
2024-06-04 21:29:14 +00:00
|
|
|
#include "media/base/media_engine.h"
|
2020-10-18 16:51:47 +00:00
|
|
|
#include "media/base/rid_description.h"
|
2024-06-04 21:29:14 +00:00
|
|
|
#include "media/base/stream_params.h"
|
2021-04-19 21:21:36 +00:00
|
|
|
#include "p2p/base/ice_transport_internal.h"
|
2020-10-18 16:51:47 +00:00
|
|
|
#include "p2p/base/p2p_constants.h"
|
|
|
|
|
#include "p2p/base/p2p_transport_channel.h"
|
2024-06-04 21:29:14 +00:00
|
|
|
#include "p2p/base/port_allocator.h"
|
2020-10-18 16:51:47 +00:00
|
|
|
#include "p2p/base/transport_description.h"
|
|
|
|
|
#include "p2p/base/transport_description_factory.h"
|
|
|
|
|
#include "p2p/base/transport_info.h"
|
2022-02-09 12:42:27 +00:00
|
|
|
#include "pc/channel_interface.h"
|
2024-06-04 21:29:14 +00:00
|
|
|
#include "pc/connection_context.h"
|
2021-04-19 21:21:36 +00:00
|
|
|
#include "pc/dtls_transport.h"
|
2024-06-04 21:29:14 +00:00
|
|
|
#include "pc/jsep_transport_controller.h"
|
2022-07-04 14:36:37 +02:00
|
|
|
#include "pc/legacy_stats_collector.h"
|
2024-06-04 21:29:14 +00:00
|
|
|
#include "pc/media_session.h"
|
2020-09-28 13:02:07 +00:00
|
|
|
#include "pc/media_stream.h"
|
2024-06-04 21:29:14 +00:00
|
|
|
#include "pc/media_stream_observer.h"
|
2021-05-26 18:56:30 +02:00
|
|
|
#include "pc/media_stream_proxy.h"
|
2022-02-08 12:55:42 +00:00
|
|
|
#include "pc/peer_connection_internal.h"
|
2020-10-05 07:01:09 +00:00
|
|
|
#include "pc/peer_connection_message_handler.h"
|
2020-09-28 13:02:07 +00:00
|
|
|
#include "pc/rtp_media_utils.h"
|
2024-06-04 21:29:14 +00:00
|
|
|
#include "pc/rtp_receiver.h"
|
2020-10-19 06:35:55 +00:00
|
|
|
#include "pc/rtp_sender.h"
|
2022-02-09 12:42:27 +00:00
|
|
|
#include "pc/rtp_sender_proxy.h"
|
2024-06-04 21:29:14 +00:00
|
|
|
#include "pc/rtp_transceiver.h"
|
|
|
|
|
#include "pc/rtp_transmission_manager.h"
|
|
|
|
|
#include "pc/session_description.h"
|
2020-10-18 16:51:47 +00:00
|
|
|
#include "pc/simulcast_description.h"
|
2024-06-04 21:29:14 +00:00
|
|
|
#include "pc/stream_collection.h"
|
|
|
|
|
#include "pc/transceiver_list.h"
|
2020-10-18 16:51:47 +00:00
|
|
|
#include "pc/usage_pattern.h"
|
2023-09-25 14:42:51 +02:00
|
|
|
#include "pc/used_ids.h"
|
2020-10-22 10:39:40 +00:00
|
|
|
#include "pc/webrtc_session_description_factory.h"
|
2024-06-04 21:29:14 +00:00
|
|
|
#include "rtc_base/checks.h"
|
2024-06-06 07:31:07 -07:00
|
|
|
#include "rtc_base/crypto_random.h"
|
2020-10-18 16:51:47 +00:00
|
|
|
#include "rtc_base/logging.h"
|
2024-06-04 21:29:14 +00:00
|
|
|
#include "rtc_base/operations_chain.h"
|
2020-10-22 10:39:40 +00:00
|
|
|
#include "rtc_base/rtc_certificate.h"
|
2020-10-18 16:51:47 +00:00
|
|
|
#include "rtc_base/ssl_stream_adapter.h"
|
|
|
|
|
#include "rtc_base/string_encode.h"
|
|
|
|
|
#include "rtc_base/strings/string_builder.h"
|
2024-06-04 21:29:14 +00:00
|
|
|
#include "rtc_base/thread.h"
|
2020-09-28 13:02:07 +00:00
|
|
|
#include "rtc_base/trace_event.h"
|
2024-06-04 21:29:14 +00:00
|
|
|
#include "rtc_base/weak_ptr.h"
|
2020-09-28 13:02:07 +00:00
|
|
|
#include "system_wrappers/include/metrics.h"
|
|
|
|
|
|
|
|
|
|
using cricket::ContentInfo;
|
|
|
|
|
using cricket::ContentInfos;
|
|
|
|
|
using cricket::MediaContentDescription;
|
|
|
|
|
using cricket::MediaProtocolType;
|
|
|
|
|
using cricket::RidDescription;
|
|
|
|
|
using cricket::RidDirection;
|
|
|
|
|
using cricket::SessionDescription;
|
|
|
|
|
using cricket::SimulcastDescription;
|
|
|
|
|
using cricket::SimulcastLayer;
|
|
|
|
|
using cricket::SimulcastLayerList;
|
|
|
|
|
using cricket::StreamParams;
|
|
|
|
|
using cricket::TransportInfo;
|
|
|
|
|
|
|
|
|
|
namespace webrtc {
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
2023-11-13 09:33:56 +00:00
|
|
|
typedef PeerConnectionInterface::RTCOfferAnswerOptions RTCOfferAnswerOptions;
|
2020-10-01 10:23:33 +00:00
|
|
|
|
2020-09-28 13:02:07 +00:00
|
|
|
// Error messages
|
|
|
|
|
const char kInvalidSdp[] = "Invalid session description.";
|
|
|
|
|
const char kInvalidCandidates[] = "Description contains invalid candidates.";
|
|
|
|
|
const char kBundleWithoutRtcpMux[] =
|
|
|
|
|
"rtcp-mux must be enabled when BUNDLE "
|
|
|
|
|
"is enabled.";
|
|
|
|
|
const char kMlineMismatchInAnswer[] =
|
|
|
|
|
"The order of m-lines in answer doesn't match order in offer. Rejecting "
|
|
|
|
|
"answer.";
|
|
|
|
|
const char kMlineMismatchInSubsequentOffer[] =
|
|
|
|
|
"The order of m-lines in subsequent offer doesn't match order from "
|
|
|
|
|
"previous offer/answer.";
|
|
|
|
|
const char kSdpWithoutIceUfragPwd[] =
|
|
|
|
|
"Called with SDP without ice-ufrag and ice-pwd.";
|
|
|
|
|
const char kSdpWithoutDtlsFingerprint[] =
|
|
|
|
|
"Called with SDP without DTLS fingerprint.";
|
2024-02-08 13:15:51 +00:00
|
|
|
const char kSdpWithoutCrypto[] = "Called with SDP without crypto setup.";
|
2020-09-28 13:02:07 +00:00
|
|
|
|
2020-10-01 16:47:23 +00:00
|
|
|
const char kSessionError[] = "Session error code: ";
|
|
|
|
|
const char kSessionErrorDesc[] = "Session error description: ";
|
|
|
|
|
|
2020-10-05 13:08:41 +00:00
|
|
|
// The length of RTCP CNAMEs.
|
|
|
|
|
static const int kRtcpCnameLength = 16;
|
|
|
|
|
|
2021-11-08 09:58:15 +01:00
|
|
|
// The maximum length of the MID attribute.
|
2022-12-07 11:49:31 +01:00
|
|
|
static constexpr size_t kMidMaxSize = 16;
|
2021-11-08 09:58:15 +01:00
|
|
|
|
2020-10-01 16:47:23 +00:00
|
|
|
const char kDefaultStreamId[] = "default";
|
|
|
|
|
// NOTE: Duplicated in peer_connection.cc:
|
|
|
|
|
static const char kDefaultAudioSenderId[] = "defaulta0";
|
|
|
|
|
static const char kDefaultVideoSenderId[] = "defaultv0";
|
|
|
|
|
|
2020-09-28 13:02:07 +00:00
|
|
|
void NoteAddIceCandidateResult(int result) {
|
|
|
|
|
RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.AddIceCandidate", result,
|
|
|
|
|
kAddIceCandidateMax);
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-26 21:04:26 +02:00
|
|
|
std::map<std::string, const cricket::ContentGroup*> GetBundleGroupsByMid(
|
|
|
|
|
const SessionDescription* desc) {
|
|
|
|
|
std::vector<const cricket::ContentGroup*> bundle_groups =
|
|
|
|
|
desc->GetGroupsByName(cricket::GROUP_TYPE_BUNDLE);
|
|
|
|
|
std::map<std::string, const cricket::ContentGroup*> bundle_groups_by_mid;
|
|
|
|
|
for (const cricket::ContentGroup* bundle_group : bundle_groups) {
|
|
|
|
|
for (const std::string& content_name : bundle_group->content_names()) {
|
|
|
|
|
bundle_groups_by_mid[content_name] = bundle_group;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return bundle_groups_by_mid;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-30 22:30:23 +02:00
|
|
|
// Returns true if `new_desc` requests an ICE restart (i.e., new ufrag/pwd).
|
2020-09-28 13:02:07 +00:00
|
|
|
bool CheckForRemoteIceRestart(const SessionDescriptionInterface* old_desc,
|
|
|
|
|
const SessionDescriptionInterface* new_desc,
|
|
|
|
|
const std::string& content_name) {
|
|
|
|
|
if (!old_desc) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
const SessionDescription* new_sd = new_desc->description();
|
|
|
|
|
const SessionDescription* old_sd = old_desc->description();
|
|
|
|
|
const ContentInfo* cinfo = new_sd->GetContentByName(content_name);
|
|
|
|
|
if (!cinfo || cinfo->rejected) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// If the content isn't rejected, check if ufrag and password has changed.
|
|
|
|
|
const cricket::TransportDescription* new_transport_desc =
|
|
|
|
|
new_sd->GetTransportDescriptionByName(content_name);
|
|
|
|
|
const cricket::TransportDescription* old_transport_desc =
|
|
|
|
|
old_sd->GetTransportDescriptionByName(content_name);
|
|
|
|
|
if (!new_transport_desc || !old_transport_desc) {
|
|
|
|
|
// No transport description exists. This is not an ICE restart.
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (cricket::IceCredentialsChanged(
|
|
|
|
|
old_transport_desc->ice_ufrag, old_transport_desc->ice_pwd,
|
|
|
|
|
new_transport_desc->ice_ufrag, new_transport_desc->ice_pwd)) {
|
|
|
|
|
RTC_LOG(LS_INFO) << "Remote peer requests ICE restart for " << content_name
|
|
|
|
|
<< ".";
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Generates a string error message for SetLocalDescription/SetRemoteDescription
|
|
|
|
|
// from an RTCError.
|
|
|
|
|
std::string GetSetDescriptionErrorMessage(cricket::ContentSource source,
|
|
|
|
|
SdpType type,
|
|
|
|
|
const RTCError& error) {
|
|
|
|
|
rtc::StringBuilder oss;
|
|
|
|
|
oss << "Failed to set " << (source == cricket::CS_LOCAL ? "local" : "remote")
|
2022-01-17 19:19:56 +01:00
|
|
|
<< " " << SdpTypeToString(type) << " sdp: ";
|
|
|
|
|
RTC_DCHECK(!absl::StartsWith(error.message(), oss.str())) << error.message();
|
|
|
|
|
oss << error.message();
|
2020-09-28 13:02:07 +00:00
|
|
|
return oss.Release();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string GetStreamIdsString(rtc::ArrayView<const std::string> stream_ids) {
|
|
|
|
|
std::string output = "streams=[";
|
|
|
|
|
const char* separator = "";
|
|
|
|
|
for (const auto& stream_id : stream_ids) {
|
|
|
|
|
output.append(separator).append(stream_id);
|
|
|
|
|
separator = ", ";
|
|
|
|
|
}
|
|
|
|
|
output.append("]");
|
|
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ContentInfo* FindTransceiverMSection(
|
2021-04-19 21:21:36 +00:00
|
|
|
RtpTransceiver* transceiver,
|
2020-09-28 13:02:07 +00:00
|
|
|
const SessionDescriptionInterface* session_description) {
|
|
|
|
|
return transceiver->mid()
|
|
|
|
|
? session_description->description()->GetContentByName(
|
|
|
|
|
*transceiver->mid())
|
|
|
|
|
: nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the direction is "recvonly" or "inactive", treat the description
|
|
|
|
|
// as containing no streams.
|
|
|
|
|
// See: https://code.google.com/p/webrtc/issues/detail?id=5054
|
|
|
|
|
std::vector<cricket::StreamParams> GetActiveStreams(
|
|
|
|
|
const cricket::MediaContentDescription* desc) {
|
|
|
|
|
return RtpTransceiverDirectionHasSend(desc->direction())
|
|
|
|
|
? desc->streams()
|
|
|
|
|
: std::vector<cricket::StreamParams>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Logic to decide if an m= section can be recycled. This means that the new
|
|
|
|
|
// m= section is not rejected, but the old local or remote m= section is
|
2021-07-30 22:30:23 +02:00
|
|
|
// rejected. `old_content_one` and `old_content_two` refer to the m= section
|
2020-09-28 13:02:07 +00:00
|
|
|
// of the old remote and old local descriptions in no particular order.
|
|
|
|
|
// We need to check both the old local and remote because either
|
|
|
|
|
// could be the most current from the latest negotation.
|
|
|
|
|
bool IsMediaSectionBeingRecycled(SdpType type,
|
|
|
|
|
const ContentInfo& content,
|
|
|
|
|
const ContentInfo* old_content_one,
|
|
|
|
|
const ContentInfo* old_content_two) {
|
|
|
|
|
return type == SdpType::kOffer && !content.rejected &&
|
|
|
|
|
((old_content_one && old_content_one->rejected) ||
|
|
|
|
|
(old_content_two && old_content_two->rejected));
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-30 22:30:23 +02:00
|
|
|
// Verify that the order of media sections in `new_desc` matches
|
|
|
|
|
// `current_desc`. The number of m= sections in `new_desc` should be no
|
|
|
|
|
// less than `current_desc`. In the case of checking an answer's
|
|
|
|
|
// `new_desc`, the `current_desc` is the last offer that was set as the
|
|
|
|
|
// local or remote. In the case of checking an offer's `new_desc` we
|
2020-09-28 13:02:07 +00:00
|
|
|
// check against the local and remote descriptions stored from the last
|
|
|
|
|
// negotiation, because either of these could be the most up to date for
|
2021-07-30 22:30:23 +02:00
|
|
|
// possible rejected m sections. These are the `current_desc` and
|
|
|
|
|
// `secondary_current_desc`.
|
2020-09-28 13:02:07 +00:00
|
|
|
bool MediaSectionsInSameOrder(const SessionDescription& current_desc,
|
|
|
|
|
const SessionDescription* secondary_current_desc,
|
|
|
|
|
const SessionDescription& new_desc,
|
|
|
|
|
const SdpType type) {
|
|
|
|
|
if (current_desc.contents().size() > new_desc.contents().size()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < current_desc.contents().size(); ++i) {
|
|
|
|
|
const cricket::ContentInfo* secondary_content_info = nullptr;
|
|
|
|
|
if (secondary_current_desc &&
|
|
|
|
|
i < secondary_current_desc->contents().size()) {
|
|
|
|
|
secondary_content_info = &secondary_current_desc->contents()[i];
|
|
|
|
|
}
|
|
|
|
|
if (IsMediaSectionBeingRecycled(type, new_desc.contents()[i],
|
|
|
|
|
¤t_desc.contents()[i],
|
|
|
|
|
secondary_content_info)) {
|
|
|
|
|
// For new offer descriptions, if the media section can be recycled, it's
|
|
|
|
|
// valid for the MID and media type to change.
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (new_desc.contents()[i].name != current_desc.contents()[i].name) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
const MediaContentDescription* new_desc_mdesc =
|
|
|
|
|
new_desc.contents()[i].media_description();
|
|
|
|
|
const MediaContentDescription* current_desc_mdesc =
|
|
|
|
|
current_desc.contents()[i].media_description();
|
|
|
|
|
if (new_desc_mdesc->type() != current_desc_mdesc->type()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool MediaSectionsHaveSameCount(const SessionDescription& desc1,
|
|
|
|
|
const SessionDescription& desc2) {
|
|
|
|
|
return desc1.contents().size() == desc2.contents().size();
|
|
|
|
|
}
|
2024-02-08 13:15:51 +00:00
|
|
|
// Checks that each non-rejected content has a DTLS
|
2020-09-28 13:02:07 +00:00
|
|
|
// fingerprint, unless it's in a BUNDLE group, in which case only the
|
|
|
|
|
// BUNDLE-tag section (first media section/description in the BUNDLE group)
|
|
|
|
|
// needs a ufrag and pwd. Mismatches, such as replying with a DTLS fingerprint
|
|
|
|
|
// to SDES keys, will be caught in JsepTransport negotiation, and backstopped
|
2021-07-30 22:30:23 +02:00
|
|
|
// by Channel's `srtp_required` check.
|
2021-04-26 21:04:26 +02:00
|
|
|
RTCError VerifyCrypto(const SessionDescription* desc,
|
2021-11-04 13:52:31 +00:00
|
|
|
bool dtls_enabled,
|
2021-04-26 21:04:26 +02:00
|
|
|
const std::map<std::string, const cricket::ContentGroup*>&
|
|
|
|
|
bundle_groups_by_mid) {
|
2020-09-28 13:02:07 +00:00
|
|
|
for (const cricket::ContentInfo& content_info : desc->contents()) {
|
|
|
|
|
if (content_info.rejected) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const std::string& mid = content_info.name;
|
2021-04-26 21:04:26 +02:00
|
|
|
auto it = bundle_groups_by_mid.find(mid);
|
|
|
|
|
const cricket::ContentGroup* bundle =
|
|
|
|
|
it != bundle_groups_by_mid.end() ? it->second : nullptr;
|
|
|
|
|
if (bundle && mid != *(bundle->FirstContentName())) {
|
2020-09-28 13:02:07 +00:00
|
|
|
// This isn't the first media section in the BUNDLE group, so it's not
|
|
|
|
|
// required to have crypto attributes, since only the crypto attributes
|
|
|
|
|
// from the first section actually get used.
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the content isn't rejected or bundled into another m= section, crypto
|
|
|
|
|
// must be present.
|
|
|
|
|
const MediaContentDescription* media = content_info.media_description();
|
|
|
|
|
const TransportInfo* tinfo = desc->GetTransportInfoByName(mid);
|
|
|
|
|
if (!media || !tinfo) {
|
|
|
|
|
// Something is not right.
|
|
|
|
|
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, kInvalidSdp);
|
|
|
|
|
}
|
2021-11-04 13:52:31 +00:00
|
|
|
if (dtls_enabled) {
|
|
|
|
|
if (!tinfo->description.identity_fingerprint) {
|
2023-07-18 11:03:39 +02:00
|
|
|
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
|
|
|
|
|
kSdpWithoutDtlsFingerprint);
|
2021-11-04 13:52:31 +00:00
|
|
|
}
|
|
|
|
|
} else {
|
2024-02-08 13:15:51 +00:00
|
|
|
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, kSdpWithoutCrypto);
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return RTCError::OK();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Checks that each non-rejected content has ice-ufrag and ice-pwd set, unless
|
|
|
|
|
// it's in a BUNDLE group, in which case only the BUNDLE-tag section (first
|
|
|
|
|
// media section/description in the BUNDLE group) needs a ufrag and pwd.
|
2021-04-26 21:04:26 +02:00
|
|
|
bool VerifyIceUfragPwdPresent(
|
|
|
|
|
const SessionDescription* desc,
|
|
|
|
|
const std::map<std::string, const cricket::ContentGroup*>&
|
|
|
|
|
bundle_groups_by_mid) {
|
2020-09-28 13:02:07 +00:00
|
|
|
for (const cricket::ContentInfo& content_info : desc->contents()) {
|
|
|
|
|
if (content_info.rejected) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const std::string& mid = content_info.name;
|
2021-04-26 21:04:26 +02:00
|
|
|
auto it = bundle_groups_by_mid.find(mid);
|
|
|
|
|
const cricket::ContentGroup* bundle =
|
|
|
|
|
it != bundle_groups_by_mid.end() ? it->second : nullptr;
|
|
|
|
|
if (bundle && mid != *(bundle->FirstContentName())) {
|
2020-09-28 13:02:07 +00:00
|
|
|
// This isn't the first media section in the BUNDLE group, so it's not
|
|
|
|
|
// required to have ufrag/password, since only the ufrag/password from
|
|
|
|
|
// the first section actually get used.
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the content isn't rejected or bundled into another m= section,
|
|
|
|
|
// ice-ufrag and ice-pwd must be present.
|
|
|
|
|
const TransportInfo* tinfo = desc->GetTransportInfoByName(mid);
|
|
|
|
|
if (!tinfo) {
|
|
|
|
|
// Something is not right.
|
|
|
|
|
RTC_LOG(LS_ERROR) << kInvalidSdp;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (tinfo->description.ice_ufrag.empty() ||
|
|
|
|
|
tinfo->description.ice_pwd.empty()) {
|
|
|
|
|
RTC_LOG(LS_ERROR) << "Session description must have ice ufrag and pwd.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-19 21:21:36 +00:00
|
|
|
RTCError ValidateMids(const cricket::SessionDescription& description) {
|
2020-09-28 13:02:07 +00:00
|
|
|
std::set<std::string> mids;
|
|
|
|
|
for (const cricket::ContentInfo& content : description.contents()) {
|
|
|
|
|
if (content.name.empty()) {
|
|
|
|
|
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
|
|
|
|
|
"A media section is missing a MID attribute.");
|
|
|
|
|
}
|
2021-11-08 09:58:15 +01:00
|
|
|
if (content.name.size() > kMidMaxSize) {
|
|
|
|
|
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
|
|
|
|
|
"The MID attribute exceeds the maximum supported "
|
2022-12-07 11:49:31 +01:00
|
|
|
"length of 16 characters.");
|
2021-11-08 09:58:15 +01:00
|
|
|
}
|
2020-09-28 13:02:07 +00:00
|
|
|
if (!mids.insert(content.name).second) {
|
|
|
|
|
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
|
|
|
|
|
"Duplicate a=mid value '" + content.name + "'.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return RTCError::OK();
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-28 17:48:04 +01:00
|
|
|
RTCError FindDuplicateCodecParameters(
|
|
|
|
|
const RtpCodecParameters codec_parameters,
|
|
|
|
|
std::map<int, RtpCodecParameters>& payload_to_codec_parameters) {
|
|
|
|
|
auto existing_codec_parameters =
|
|
|
|
|
payload_to_codec_parameters.find(codec_parameters.payload_type);
|
|
|
|
|
if (existing_codec_parameters != payload_to_codec_parameters.end() &&
|
|
|
|
|
codec_parameters != existing_codec_parameters->second) {
|
2023-07-18 11:03:39 +02:00
|
|
|
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
|
|
|
|
|
"A BUNDLE group contains a codec collision for "
|
|
|
|
|
"payload_type='" +
|
|
|
|
|
rtc::ToString(codec_parameters.payload_type) +
|
|
|
|
|
". All codecs must share the same type, "
|
|
|
|
|
"encoding name, clock rate and parameters.");
|
2022-11-28 17:48:04 +01:00
|
|
|
}
|
|
|
|
|
payload_to_codec_parameters.insert(
|
|
|
|
|
std::make_pair(codec_parameters.payload_type, codec_parameters));
|
|
|
|
|
return RTCError::OK();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RTCError ValidateBundledPayloadTypes(
|
|
|
|
|
const cricket::SessionDescription& description) {
|
|
|
|
|
// https://www.rfc-editor.org/rfc/rfc8843#name-payload-type-pt-value-reuse
|
|
|
|
|
// ... all codecs associated with the payload type number MUST share an
|
|
|
|
|
// identical codec configuration. This means that the codecs MUST share
|
|
|
|
|
// the same media type, encoding name, clock rate, and any parameter
|
|
|
|
|
// that can affect the codec configuration and packetization.
|
|
|
|
|
std::vector<const cricket::ContentGroup*> bundle_groups =
|
|
|
|
|
description.GetGroupsByName(cricket::GROUP_TYPE_BUNDLE);
|
|
|
|
|
for (const cricket::ContentGroup* bundle_group : bundle_groups) {
|
|
|
|
|
std::map<int, RtpCodecParameters> payload_to_codec_parameters;
|
|
|
|
|
for (const std::string& content_name : bundle_group->content_names()) {
|
2023-08-25 12:42:36 +02:00
|
|
|
const ContentInfo* content_description =
|
|
|
|
|
description.GetContentByName(content_name);
|
|
|
|
|
if (!content_description) {
|
2023-07-18 11:03:39 +02:00
|
|
|
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
|
|
|
|
|
"A BUNDLE group contains a MID='" + content_name +
|
|
|
|
|
"' matching no m= section.");
|
2022-11-28 17:48:04 +01:00
|
|
|
}
|
2023-08-25 12:42:36 +02:00
|
|
|
const cricket::MediaContentDescription* media_description =
|
|
|
|
|
content_description->media_description();
|
|
|
|
|
RTC_DCHECK(media_description);
|
|
|
|
|
if (content_description->rejected || !media_description ||
|
|
|
|
|
!media_description->has_codecs()) {
|
2022-11-28 17:48:04 +01:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const auto type = media_description->type();
|
2023-09-18 14:20:34 +02:00
|
|
|
if (type == cricket::MEDIA_TYPE_AUDIO ||
|
|
|
|
|
type == cricket::MEDIA_TYPE_VIDEO) {
|
|
|
|
|
for (const auto& c : media_description->codecs()) {
|
2022-11-28 17:48:04 +01:00
|
|
|
auto error = FindDuplicateCodecParameters(
|
|
|
|
|
c.ToCodecParameters(), payload_to_codec_parameters);
|
|
|
|
|
if (!error.ok()) {
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return RTCError::OK();
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-19 11:04:06 +01:00
|
|
|
RTCError FindDuplicateHeaderExtensionIds(
|
|
|
|
|
const RtpExtension extension,
|
|
|
|
|
std::map<int, RtpExtension>& id_to_extension) {
|
|
|
|
|
auto existing_extension = id_to_extension.find(extension.id);
|
|
|
|
|
if (existing_extension != id_to_extension.end() &&
|
|
|
|
|
!(extension.uri == existing_extension->second.uri &&
|
|
|
|
|
extension.encrypt == existing_extension->second.encrypt)) {
|
2023-07-18 11:03:39 +02:00
|
|
|
LOG_AND_RETURN_ERROR(
|
2022-12-19 11:04:06 +01:00
|
|
|
RTCErrorType::INVALID_PARAMETER,
|
|
|
|
|
"A BUNDLE group contains a codec collision for "
|
2023-05-25 16:11:36 +02:00
|
|
|
"header extension id=" +
|
2022-12-19 11:04:06 +01:00
|
|
|
rtc::ToString(extension.id) +
|
|
|
|
|
". The id must be the same across all bundled media descriptions");
|
|
|
|
|
}
|
|
|
|
|
id_to_extension.insert(std::make_pair(extension.id, extension));
|
|
|
|
|
return RTCError::OK();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RTCError ValidateBundledRtpHeaderExtensions(
|
|
|
|
|
const cricket::SessionDescription& description) {
|
|
|
|
|
// https://www.rfc-editor.org/rfc/rfc8843#name-rtp-header-extensions-consi
|
|
|
|
|
// ... the identifier used for a given extension MUST identify the same
|
|
|
|
|
// extension across all the bundled media descriptions.
|
|
|
|
|
std::vector<const cricket::ContentGroup*> bundle_groups =
|
|
|
|
|
description.GetGroupsByName(cricket::GROUP_TYPE_BUNDLE);
|
|
|
|
|
for (const cricket::ContentGroup* bundle_group : bundle_groups) {
|
|
|
|
|
std::map<int, RtpExtension> id_to_extension;
|
|
|
|
|
for (const std::string& content_name : bundle_group->content_names()) {
|
2023-08-25 12:42:36 +02:00
|
|
|
const ContentInfo* content_description =
|
|
|
|
|
description.GetContentByName(content_name);
|
|
|
|
|
if (!content_description) {
|
2023-07-18 11:03:39 +02:00
|
|
|
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
|
|
|
|
|
"A BUNDLE group contains a MID='" + content_name +
|
|
|
|
|
"' matching no m= section.");
|
2022-12-19 11:04:06 +01:00
|
|
|
}
|
2023-08-25 12:42:36 +02:00
|
|
|
const cricket::MediaContentDescription* media_description =
|
|
|
|
|
content_description->media_description();
|
|
|
|
|
RTC_DCHECK(media_description);
|
|
|
|
|
if (content_description->rejected || !media_description ||
|
|
|
|
|
!media_description->has_codecs()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-19 11:04:06 +01:00
|
|
|
for (const auto& extension : media_description->rtp_header_extensions()) {
|
|
|
|
|
auto error =
|
|
|
|
|
FindDuplicateHeaderExtensionIds(extension, id_to_extension);
|
|
|
|
|
if (!error.ok()) {
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return RTCError::OK();
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-01 19:07:50 +02:00
|
|
|
RTCError ValidateRtpHeaderExtensionsForSpecSimulcast(
|
|
|
|
|
const cricket::SessionDescription& description) {
|
|
|
|
|
for (const ContentInfo& content : description.contents()) {
|
2023-08-25 12:42:36 +02:00
|
|
|
if (content.type != MediaProtocolType::kRtp || content.rejected) {
|
2023-06-01 19:07:50 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const auto media_description = content.media_description();
|
|
|
|
|
if (!media_description->HasSimulcast()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
auto extensions = media_description->rtp_header_extensions();
|
|
|
|
|
auto it = absl::c_find_if(extensions, [](const RtpExtension& ext) {
|
|
|
|
|
return ext.uri == RtpExtension::kRidUri;
|
|
|
|
|
});
|
|
|
|
|
if (it == extensions.end()) {
|
2023-07-18 11:03:39 +02:00
|
|
|
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
|
|
|
|
|
"The media section with MID='" + content.mid() +
|
|
|
|
|
"' negotiates simulcast but does not negotiate "
|
|
|
|
|
"the RID RTP header extension.");
|
2023-06-01 19:07:50 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return RTCError::OK();
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-11 09:32:50 +02:00
|
|
|
RTCError ValidateSsrcGroups(const cricket::SessionDescription& description) {
|
|
|
|
|
for (const ContentInfo& content : description.contents()) {
|
|
|
|
|
if (content.type != MediaProtocolType::kRtp) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
for (const StreamParams& stream : content.media_description()->streams()) {
|
|
|
|
|
for (const cricket::SsrcGroup& group : stream.ssrc_groups) {
|
|
|
|
|
// Validate the number of SSRCs for standard SSRC group semantics such
|
|
|
|
|
// as FID and FEC-FR and the non-standard SIM group.
|
|
|
|
|
if ((group.semantics == cricket::kFidSsrcGroupSemantics &&
|
|
|
|
|
group.ssrcs.size() != 2) ||
|
|
|
|
|
(group.semantics == cricket::kFecFrSsrcGroupSemantics &&
|
|
|
|
|
group.ssrcs.size() != 2) ||
|
|
|
|
|
(group.semantics == cricket::kSimSsrcGroupSemantics &&
|
|
|
|
|
group.ssrcs.size() > kMaxSimulcastStreams)) {
|
|
|
|
|
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
|
|
|
|
|
"The media section with MID='" + content.mid() +
|
|
|
|
|
"' has a ssrc-group with semantics " +
|
|
|
|
|
group.semantics +
|
|
|
|
|
" and an unexpected number of SSRCs.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return RTCError::OK();
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-14 16:23:31 +02:00
|
|
|
RTCError ValidatePayloadTypes(const cricket::SessionDescription& description) {
|
|
|
|
|
for (const ContentInfo& content : description.contents()) {
|
|
|
|
|
if (content.type != MediaProtocolType::kRtp) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const auto media_description = content.media_description();
|
|
|
|
|
RTC_DCHECK(media_description);
|
|
|
|
|
if (content.rejected || !media_description ||
|
|
|
|
|
!media_description->has_codecs()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const auto type = media_description->type();
|
2023-11-23 20:21:05 +01:00
|
|
|
if (type == cricket::MEDIA_TYPE_AUDIO ||
|
|
|
|
|
type == cricket::MEDIA_TYPE_VIDEO) {
|
|
|
|
|
for (const auto& codec : media_description->codecs()) {
|
2023-09-25 14:42:51 +02:00
|
|
|
if (!cricket::UsedPayloadTypes::IsIdValid(
|
|
|
|
|
codec, media_description->rtcp_mux())) {
|
2023-09-14 16:23:31 +02:00
|
|
|
LOG_AND_RETURN_ERROR(
|
|
|
|
|
RTCErrorType::INVALID_PARAMETER,
|
|
|
|
|
"The media section with MID='" + content.mid() +
|
|
|
|
|
"' used an invalid payload type " + rtc::ToString(codec.id) +
|
|
|
|
|
" for codec '" + codec.name + ", rtcp-mux:" +
|
|
|
|
|
(media_description->rtcp_mux() ? "enabled" : "disabled"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return RTCError::OK();
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-28 13:02:07 +00:00
|
|
|
bool IsValidOfferToReceiveMedia(int value) {
|
|
|
|
|
typedef PeerConnectionInterface::RTCOfferAnswerOptions Options;
|
|
|
|
|
return (value >= Options::kUndefined) &&
|
|
|
|
|
(value <= Options::kMaxOfferToReceiveMedia);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ValidateOfferAnswerOptions(
|
|
|
|
|
const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options) {
|
|
|
|
|
return IsValidOfferToReceiveMedia(rtc_options.offer_to_receive_audio) &&
|
|
|
|
|
IsValidOfferToReceiveMedia(rtc_options.offer_to_receive_video);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This method will extract any send encodings that were sent by the remote
|
|
|
|
|
// connection. This is currently only relevant for Simulcast scenario (where
|
|
|
|
|
// the number of layers may be communicated by the server).
|
2021-04-19 21:21:36 +00:00
|
|
|
std::vector<RtpEncodingParameters> GetSendEncodingsFromRemoteDescription(
|
2020-09-28 13:02:07 +00:00
|
|
|
const MediaContentDescription& desc) {
|
|
|
|
|
if (!desc.HasSimulcast()) {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
std::vector<RtpEncodingParameters> result;
|
|
|
|
|
const SimulcastDescription& simulcast = desc.simulcast_description();
|
|
|
|
|
|
|
|
|
|
// This is a remote description, the parameters we are after should appear
|
|
|
|
|
// as receive streams.
|
|
|
|
|
for (const auto& alternatives : simulcast.receive_layers()) {
|
|
|
|
|
RTC_DCHECK(!alternatives.empty());
|
|
|
|
|
// There is currently no way to specify or choose from alternatives.
|
|
|
|
|
// We will always use the first alternative, which is the most preferred.
|
|
|
|
|
const SimulcastLayer& layer = alternatives[0];
|
|
|
|
|
RtpEncodingParameters parameters;
|
|
|
|
|
parameters.rid = layer.rid;
|
|
|
|
|
parameters.active = !layer.is_paused;
|
|
|
|
|
result.push_back(parameters);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-19 21:21:36 +00:00
|
|
|
RTCError UpdateSimulcastLayerStatusInSender(
|
2020-09-28 13:02:07 +00:00
|
|
|
const std::vector<SimulcastLayer>& layers,
|
|
|
|
|
rtc::scoped_refptr<RtpSenderInternal> sender) {
|
|
|
|
|
RTC_DCHECK(sender);
|
2022-08-25 11:31:01 +00:00
|
|
|
RtpParameters parameters = sender->GetParametersInternalWithAllLayers();
|
2020-09-28 13:02:07 +00:00
|
|
|
std::vector<std::string> disabled_layers;
|
|
|
|
|
|
|
|
|
|
// The simulcast envelope cannot be changed, only the status of the streams.
|
|
|
|
|
// So we will iterate over the send encodings rather than the layers.
|
|
|
|
|
for (RtpEncodingParameters& encoding : parameters.encodings) {
|
|
|
|
|
auto iter = std::find_if(layers.begin(), layers.end(),
|
|
|
|
|
[&encoding](const SimulcastLayer& layer) {
|
|
|
|
|
return layer.rid == encoding.rid;
|
|
|
|
|
});
|
|
|
|
|
// A layer that cannot be found may have been removed by the remote party.
|
|
|
|
|
if (iter == layers.end()) {
|
|
|
|
|
disabled_layers.push_back(encoding.rid);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
encoding.active = !iter->is_paused;
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-25 11:31:01 +00:00
|
|
|
RTCError result = sender->SetParametersInternalWithAllLayers(parameters);
|
2020-09-28 13:02:07 +00:00
|
|
|
if (result.ok()) {
|
|
|
|
|
result = sender->DisableEncodingLayers(disabled_layers);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-19 21:21:36 +00:00
|
|
|
bool SimulcastIsRejected(const ContentInfo* local_content,
|
2021-05-17 16:06:37 +02:00
|
|
|
const MediaContentDescription& answer_media_desc,
|
|
|
|
|
bool enable_encrypted_rtp_header_extensions) {
|
2020-09-28 13:02:07 +00:00
|
|
|
bool simulcast_offered = local_content &&
|
|
|
|
|
local_content->media_description() &&
|
|
|
|
|
local_content->media_description()->HasSimulcast();
|
|
|
|
|
bool simulcast_answered = answer_media_desc.HasSimulcast();
|
|
|
|
|
bool rids_supported = RtpExtension::FindHeaderExtensionByUri(
|
2021-05-17 16:06:37 +02:00
|
|
|
answer_media_desc.rtp_header_extensions(), RtpExtension::kRidUri,
|
|
|
|
|
enable_encrypted_rtp_header_extensions
|
|
|
|
|
? RtpExtension::Filter::kPreferEncryptedExtension
|
|
|
|
|
: RtpExtension::Filter::kDiscardEncryptedExtension);
|
2020-09-28 13:02:07 +00:00
|
|
|
return simulcast_offered && (!simulcast_answered || !rids_supported);
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-19 21:21:36 +00:00
|
|
|
RTCError DisableSimulcastInSender(
|
2020-09-28 13:02:07 +00:00
|
|
|
rtc::scoped_refptr<RtpSenderInternal> sender) {
|
|
|
|
|
RTC_DCHECK(sender);
|
2022-08-25 11:31:01 +00:00
|
|
|
RtpParameters parameters = sender->GetParametersInternalWithAllLayers();
|
2020-09-28 13:02:07 +00:00
|
|
|
if (parameters.encodings.size() <= 1) {
|
|
|
|
|
return RTCError::OK();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<std::string> disabled_layers;
|
|
|
|
|
std::transform(
|
|
|
|
|
parameters.encodings.begin() + 1, parameters.encodings.end(),
|
|
|
|
|
std::back_inserter(disabled_layers),
|
|
|
|
|
[](const RtpEncodingParameters& encoding) { return encoding.rid; });
|
|
|
|
|
return sender->DisableEncodingLayers(disabled_layers);
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-01 10:23:33 +00:00
|
|
|
// The SDP parser used to populate these values by default for the 'content
|
|
|
|
|
// name' if an a=mid line was absent.
|
2021-04-19 21:21:36 +00:00
|
|
|
absl::string_view GetDefaultMidForPlanB(cricket::MediaType media_type) {
|
2020-10-01 10:23:33 +00:00
|
|
|
switch (media_type) {
|
|
|
|
|
case cricket::MEDIA_TYPE_AUDIO:
|
|
|
|
|
return cricket::CN_AUDIO;
|
|
|
|
|
case cricket::MEDIA_TYPE_VIDEO:
|
|
|
|
|
return cricket::CN_VIDEO;
|
|
|
|
|
case cricket::MEDIA_TYPE_DATA:
|
|
|
|
|
return cricket::CN_DATA;
|
2020-10-13 12:43:15 +02:00
|
|
|
case cricket::MEDIA_TYPE_UNSUPPORTED:
|
|
|
|
|
return "not supported";
|
2020-10-01 10:23:33 +00:00
|
|
|
}
|
2021-11-15 16:57:07 +01:00
|
|
|
RTC_DCHECK_NOTREACHED();
|
2020-10-01 10:23:33 +00:00
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-30 22:30:23 +02:00
|
|
|
// Add options to |[audio/video]_media_description_options| from `senders`.
|
2020-10-01 10:23:33 +00:00
|
|
|
void AddPlanBRtpSenderOptions(
|
|
|
|
|
const std::vector<rtc::scoped_refptr<
|
|
|
|
|
RtpSenderProxyWithInternal<RtpSenderInternal>>>& senders,
|
|
|
|
|
cricket::MediaDescriptionOptions* audio_media_description_options,
|
|
|
|
|
cricket::MediaDescriptionOptions* video_media_description_options,
|
|
|
|
|
int num_sim_layers) {
|
|
|
|
|
for (const auto& sender : senders) {
|
|
|
|
|
if (sender->media_type() == cricket::MEDIA_TYPE_AUDIO) {
|
|
|
|
|
if (audio_media_description_options) {
|
|
|
|
|
audio_media_description_options->AddAudioSender(
|
|
|
|
|
sender->id(), sender->internal()->stream_ids());
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
RTC_DCHECK(sender->media_type() == cricket::MEDIA_TYPE_VIDEO);
|
|
|
|
|
if (video_media_description_options) {
|
|
|
|
|
video_media_description_options->AddVideoSender(
|
|
|
|
|
sender->id(), sender->internal()->stream_ids(), {},
|
|
|
|
|
SimulcastLayerList(), num_sim_layers);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-19 21:21:36 +00:00
|
|
|
cricket::MediaDescriptionOptions GetMediaDescriptionOptionsForTransceiver(
|
|
|
|
|
RtpTransceiver* transceiver,
|
2020-10-01 10:23:33 +00:00
|
|
|
const std::string& mid,
|
|
|
|
|
bool is_create_offer) {
|
|
|
|
|
// NOTE: a stopping transceiver should be treated as a stopped one in
|
|
|
|
|
// createOffer as specified in
|
|
|
|
|
// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-createoffer.
|
|
|
|
|
bool stopped =
|
|
|
|
|
is_create_offer ? transceiver->stopping() : transceiver->stopped();
|
|
|
|
|
cricket::MediaDescriptionOptions media_description_options(
|
|
|
|
|
transceiver->media_type(), mid, transceiver->direction(), stopped);
|
|
|
|
|
media_description_options.codec_preferences =
|
|
|
|
|
transceiver->codec_preferences();
|
|
|
|
|
media_description_options.header_extensions =
|
2023-03-06 18:08:31 +01:00
|
|
|
transceiver->GetHeaderExtensionsToNegotiate();
|
2020-10-01 10:23:33 +00:00
|
|
|
// This behavior is specified in JSEP. The gist is that:
|
|
|
|
|
// 1. The MSID is included if the RtpTransceiver's direction is sendonly or
|
|
|
|
|
// sendrecv.
|
|
|
|
|
// 2. If the MSID is included, then it must be included in any subsequent
|
|
|
|
|
// offer/answer exactly the same until the RtpTransceiver is stopped.
|
|
|
|
|
if (stopped || (!RtpTransceiverDirectionHasSend(transceiver->direction()) &&
|
2021-04-19 21:21:36 +00:00
|
|
|
!transceiver->has_ever_been_used_to_send())) {
|
2020-10-01 10:23:33 +00:00
|
|
|
return media_description_options;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cricket::SenderOptions sender_options;
|
|
|
|
|
sender_options.track_id = transceiver->sender()->id();
|
|
|
|
|
sender_options.stream_ids = transceiver->sender()->stream_ids();
|
|
|
|
|
|
|
|
|
|
// The following sets up RIDs and Simulcast.
|
|
|
|
|
// RIDs are included if Simulcast is requested or if any RID was specified.
|
|
|
|
|
RtpParameters send_parameters =
|
2022-08-25 11:31:01 +00:00
|
|
|
transceiver->sender_internal()->GetParametersInternalWithAllLayers();
|
2020-10-01 10:23:33 +00:00
|
|
|
bool has_rids = std::any_of(send_parameters.encodings.begin(),
|
|
|
|
|
send_parameters.encodings.end(),
|
|
|
|
|
[](const RtpEncodingParameters& encoding) {
|
|
|
|
|
return !encoding.rid.empty();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
std::vector<RidDescription> send_rids;
|
|
|
|
|
SimulcastLayerList send_layers;
|
|
|
|
|
for (const RtpEncodingParameters& encoding : send_parameters.encodings) {
|
|
|
|
|
if (encoding.rid.empty()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2024-09-06 21:44:08 +09:00
|
|
|
auto send_rid = RidDescription(encoding.rid, RidDirection::kSend);
|
|
|
|
|
if (encoding.codec) {
|
|
|
|
|
auto send_codecs = transceiver->sender_internal()->GetSendCodecs();
|
|
|
|
|
for (const cricket::Codec& codec : send_codecs) {
|
|
|
|
|
if (codec.MatchesRtpCodec(*encoding.codec)) {
|
|
|
|
|
send_rid.payload_types.push_back(codec.id);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
send_rids.push_back(send_rid);
|
2020-10-01 10:23:33 +00:00
|
|
|
send_layers.AddLayer(SimulcastLayer(encoding.rid, !encoding.active));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (has_rids) {
|
|
|
|
|
sender_options.rids = send_rids;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sender_options.simulcast_layers = send_layers;
|
|
|
|
|
// When RIDs are configured, we must set num_sim_layers to 0 to.
|
|
|
|
|
// Otherwise, num_sim_layers must be 1 because either there is no
|
|
|
|
|
// simulcast, or simulcast is acheived by munging the SDP.
|
|
|
|
|
sender_options.num_sim_layers = has_rids ? 0 : 1;
|
|
|
|
|
media_description_options.sender_options.push_back(sender_options);
|
|
|
|
|
|
|
|
|
|
return media_description_options;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-30 22:30:23 +02:00
|
|
|
// Returns the ContentInfo at mline index `i`, or null if none exists.
|
2021-04-19 21:21:36 +00:00
|
|
|
const ContentInfo* GetContentByIndex(const SessionDescriptionInterface* sdesc,
|
|
|
|
|
size_t i) {
|
2020-10-01 10:23:33 +00:00
|
|
|
if (!sdesc) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
const ContentInfos& contents = sdesc->description()->contents();
|
|
|
|
|
return (i < contents.size() ? &contents[i] : nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-30 22:30:23 +02:00
|
|
|
// From `rtc_options`, fill parts of `session_options` shared by all generated
|
2020-10-01 10:23:33 +00:00
|
|
|
// m= sectionss (in other words, nothing that involves a map/array).
|
|
|
|
|
void ExtractSharedMediaSessionOptions(
|
|
|
|
|
const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options,
|
|
|
|
|
cricket::MediaSessionOptions* session_options) {
|
|
|
|
|
session_options->vad_enabled = rtc_options.voice_activity_detection;
|
|
|
|
|
session_options->bundle_enabled = rtc_options.use_rtp_mux;
|
|
|
|
|
session_options->raw_packetization_for_video =
|
|
|
|
|
rtc_options.raw_packetization_for_video;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-05 13:08:41 +00:00
|
|
|
// Generate a RTCP CNAME when a PeerConnection is created.
|
|
|
|
|
std::string GenerateRtcpCname() {
|
|
|
|
|
std::string cname;
|
|
|
|
|
if (!rtc::CreateRandomString(kRtcpCnameLength, &cname)) {
|
|
|
|
|
RTC_LOG(LS_ERROR) << "Failed to generate CNAME.";
|
2021-11-15 16:57:07 +01:00
|
|
|
RTC_DCHECK_NOTREACHED();
|
2020-10-05 13:08:41 +00:00
|
|
|
}
|
|
|
|
|
return cname;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-30 22:30:23 +02:00
|
|
|
// Check if we can send `new_stream` on a PeerConnection.
|
2023-11-13 09:33:56 +00:00
|
|
|
bool CanAddLocalMediaStream(StreamCollectionInterface* current_streams,
|
|
|
|
|
MediaStreamInterface* new_stream) {
|
2020-10-09 11:42:17 +00:00
|
|
|
if (!new_stream || !current_streams) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (current_streams->find(new_stream->id()) != nullptr) {
|
|
|
|
|
RTC_LOG(LS_ERROR) << "MediaStream with ID " << new_stream->id()
|
|
|
|
|
<< " is already added.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-13 09:33:56 +00:00
|
|
|
rtc::scoped_refptr<DtlsTransport> LookupDtlsTransportByMid(
|
2021-02-10 13:05:44 +01:00
|
|
|
rtc::Thread* network_thread,
|
|
|
|
|
JsepTransportController* controller,
|
|
|
|
|
const std::string& mid) {
|
|
|
|
|
// TODO(tommi): Can we post this (and associated operations where this
|
2022-09-08 18:38:10 +02:00
|
|
|
// function is called) to the network thread and avoid this BlockingCall?
|
2021-02-10 13:05:44 +01:00
|
|
|
// We might be able to simplify a few things if we set the transport on
|
|
|
|
|
// the network thread and then update the implementation to check that
|
|
|
|
|
// the set_ and relevant get methods are always called on the network
|
|
|
|
|
// thread (we'll need to update proxy maps).
|
2022-09-08 18:38:10 +02:00
|
|
|
return network_thread->BlockingCall(
|
2021-02-10 13:05:44 +01:00
|
|
|
[controller, &mid] { return controller->LookupDtlsTransportByMid(mid); });
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-09 10:29:50 +02:00
|
|
|
bool ContentHasHeaderExtension(const cricket::ContentInfo& content_info,
|
|
|
|
|
absl::string_view header_extension_uri) {
|
|
|
|
|
for (const RtpExtension& rtp_header_extension :
|
|
|
|
|
content_info.media_description()->rtp_header_extensions()) {
|
|
|
|
|
if (rtp_header_extension.uri == header_extension_uri) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-28 13:02:07 +00:00
|
|
|
} // namespace
|
|
|
|
|
|
2023-03-30 13:51:39 +02:00
|
|
|
void UpdateRtpHeaderExtensionPreferencesFromSdpMunging(
|
|
|
|
|
const cricket::SessionDescription* description,
|
|
|
|
|
TransceiverList* transceivers) {
|
|
|
|
|
// This integrates the RTP Header Extension Control API and local SDP munging
|
|
|
|
|
// for backward compability reasons. If something was enabled in the local
|
|
|
|
|
// description via SDP munging, consider it non-stopped in the API as well
|
|
|
|
|
// so that is shows up in subsequent offers/answers.
|
|
|
|
|
RTC_DCHECK(description);
|
|
|
|
|
RTC_DCHECK(transceivers);
|
|
|
|
|
for (const auto& content : description->contents()) {
|
|
|
|
|
auto transceiver = transceivers->FindByMid(content.name);
|
|
|
|
|
if (!transceiver) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
auto extension_capabilities = transceiver->GetHeaderExtensionsToNegotiate();
|
|
|
|
|
// Set the capability of every extension we see here to "sendrecv".
|
|
|
|
|
for (auto& ext : content.media_description()->rtp_header_extensions()) {
|
|
|
|
|
auto it = absl::c_find_if(extension_capabilities,
|
|
|
|
|
[&ext](const RtpHeaderExtensionCapability c) {
|
|
|
|
|
return ext.uri == c.uri;
|
|
|
|
|
});
|
|
|
|
|
if (it != extension_capabilities.end()) {
|
|
|
|
|
it->direction = RtpTransceiverDirection::kSendRecv;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
transceiver->SetHeaderExtensionsToNegotiate(extension_capabilities);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-12 13:11:04 +01:00
|
|
|
// This class stores state related to a SetRemoteDescription operation, captures
|
2023-03-30 13:51:39 +02:00
|
|
|
// and reports potential errors that might occur and makes sure to notify the
|
2022-01-12 13:11:04 +01:00
|
|
|
// observer of the operation and the operations chain of completion.
|
|
|
|
|
class SdpOfferAnswerHandler::RemoteDescriptionOperation {
|
|
|
|
|
public:
|
|
|
|
|
RemoteDescriptionOperation(
|
|
|
|
|
SdpOfferAnswerHandler* handler,
|
|
|
|
|
std::unique_ptr<SessionDescriptionInterface> desc,
|
|
|
|
|
rtc::scoped_refptr<SetRemoteDescriptionObserverInterface> observer,
|
|
|
|
|
std::function<void()> operations_chain_callback)
|
|
|
|
|
: handler_(handler),
|
|
|
|
|
desc_(std::move(desc)),
|
|
|
|
|
observer_(std::move(observer)),
|
2022-01-17 19:19:56 +01:00
|
|
|
operations_chain_callback_(std::move(operations_chain_callback)),
|
|
|
|
|
unified_plan_(handler_->IsUnifiedPlan()) {
|
2022-01-12 13:11:04 +01:00
|
|
|
if (!desc_) {
|
2022-01-17 19:19:56 +01:00
|
|
|
type_ = static_cast<SdpType>(-1);
|
2022-01-12 13:11:04 +01:00
|
|
|
InvalidParam("SessionDescription is NULL.");
|
|
|
|
|
} else {
|
|
|
|
|
type_ = desc_->GetType();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
~RemoteDescriptionOperation() {
|
|
|
|
|
RTC_DCHECK_RUN_ON(handler_->signaling_thread());
|
|
|
|
|
SignalCompletion();
|
|
|
|
|
operations_chain_callback_();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ok() const { return error_.ok(); }
|
|
|
|
|
|
|
|
|
|
// Notifies the observer that the operation is complete and releases the
|
|
|
|
|
// reference to the observer.
|
|
|
|
|
void SignalCompletion() {
|
2022-01-17 19:19:56 +01:00
|
|
|
if (!observer_)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (!error_.ok() && type_ != static_cast<SdpType>(-1)) {
|
|
|
|
|
std::string error_message =
|
|
|
|
|
GetSetDescriptionErrorMessage(cricket::CS_REMOTE, type_, error_);
|
|
|
|
|
RTC_LOG(LS_ERROR) << error_message;
|
2023-03-20 17:37:22 +01:00
|
|
|
error_.set_message(error_message);
|
2022-01-12 13:11:04 +01:00
|
|
|
}
|
2022-01-17 19:19:56 +01:00
|
|
|
|
|
|
|
|
observer_->OnSetRemoteDescriptionComplete(error_);
|
|
|
|
|
observer_ = nullptr; // Only fire the notification once.
|
2022-01-12 13:11:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If a session error has occurred the PeerConnection is in a possibly
|
|
|
|
|
// inconsistent state so fail right away.
|
|
|
|
|
bool HaveSessionError() {
|
|
|
|
|
RTC_DCHECK(ok());
|
2022-01-17 19:19:56 +01:00
|
|
|
if (handler_->session_error() != SessionError::kNone)
|
|
|
|
|
InternalError(handler_->GetSessionErrorMsg());
|
|
|
|
|
return !ok();
|
2022-01-12 13:11:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Returns true if the operation was a rollback operation. If this function
|
|
|
|
|
// returns true, the caller should consider the operation complete. Otherwise
|
|
|
|
|
// proceed to the next step.
|
|
|
|
|
bool MaybeRollback() {
|
|
|
|
|
RTC_DCHECK_RUN_ON(handler_->signaling_thread());
|
|
|
|
|
RTC_DCHECK(ok());
|
|
|
|
|
if (type_ != SdpType::kRollback) {
|
|
|
|
|
// Check if we can do an implicit rollback.
|
2022-01-17 19:19:56 +01:00
|
|
|
if (type_ == SdpType::kOffer && unified_plan_ &&
|
2022-01-12 13:11:04 +01:00
|
|
|
handler_->pc_->configuration()->enable_implicit_rollback &&
|
|
|
|
|
handler_->signaling_state() ==
|
|
|
|
|
PeerConnectionInterface::kHaveLocalOffer) {
|
|
|
|
|
handler_->Rollback(type_);
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-17 19:19:56 +01:00
|
|
|
if (unified_plan_) {
|
2022-01-12 13:11:04 +01:00
|
|
|
error_ = handler_->Rollback(type_);
|
|
|
|
|
} else if (type_ == SdpType::kRollback) {
|
|
|
|
|
Unsupported("Rollback not supported in Plan B");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Report to UMA the format of the received offer or answer.
|
|
|
|
|
void ReportOfferAnswerUma() {
|
|
|
|
|
RTC_DCHECK(ok());
|
|
|
|
|
if (type_ == SdpType::kOffer || type_ == SdpType::kAnswer) {
|
|
|
|
|
handler_->pc_->ReportSdpBundleUsage(*desc_.get());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Checks if the session description for the operation is valid. If not, the
|
|
|
|
|
// function captures error information and returns false. Note that if the
|
|
|
|
|
// return value is false, the operation should be considered done.
|
|
|
|
|
bool IsDescriptionValid() {
|
|
|
|
|
RTC_DCHECK_RUN_ON(handler_->signaling_thread());
|
|
|
|
|
RTC_DCHECK(ok());
|
|
|
|
|
RTC_DCHECK(bundle_groups_by_mid_.empty()) << "Already called?";
|
|
|
|
|
bundle_groups_by_mid_ = GetBundleGroupsByMid(description());
|
|
|
|
|
error_ = handler_->ValidateSessionDescription(
|
|
|
|
|
desc_.get(), cricket::CS_REMOTE, bundle_groups_by_mid_);
|
2022-01-17 19:19:56 +01:00
|
|
|
return ok();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Transfers ownership of the session description object over to `handler_`.
|
2023-05-02 11:28:10 +02:00
|
|
|
bool ReplaceRemoteDescriptionAndCheckError() {
|
2022-01-17 19:19:56 +01:00
|
|
|
RTC_DCHECK_RUN_ON(handler_->signaling_thread());
|
|
|
|
|
RTC_DCHECK(ok());
|
|
|
|
|
RTC_DCHECK(desc_);
|
|
|
|
|
RTC_DCHECK(!replaced_remote_description_);
|
|
|
|
|
#if RTC_DCHECK_IS_ON
|
|
|
|
|
const auto* existing_remote_description = handler_->remote_description();
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
error_ = handler_->ReplaceRemoteDescription(std::move(desc_), type_,
|
|
|
|
|
&replaced_remote_description_);
|
|
|
|
|
|
|
|
|
|
if (ok()) {
|
|
|
|
|
#if RTC_DCHECK_IS_ON
|
|
|
|
|
// Sanity check that our `old_remote_description()` method always returns
|
|
|
|
|
// the same value as `remote_description()` did before the call to
|
|
|
|
|
// ReplaceRemoteDescription.
|
|
|
|
|
RTC_DCHECK_EQ(existing_remote_description, old_remote_description());
|
|
|
|
|
#endif
|
|
|
|
|
} else {
|
|
|
|
|
SetAsSessionError();
|
2022-01-12 13:11:04 +01:00
|
|
|
}
|
|
|
|
|
|
2022-01-17 19:19:56 +01:00
|
|
|
return ok();
|
2022-01-12 13:11:04 +01:00
|
|
|
}
|
|
|
|
|
|
2022-01-17 19:19:56 +01:00
|
|
|
bool UpdateChannels() {
|
2022-01-12 13:11:04 +01:00
|
|
|
RTC_DCHECK(ok());
|
2022-01-17 19:19:56 +01:00
|
|
|
RTC_DCHECK(!desc_) << "ReplaceRemoteDescription hasn't been called";
|
|
|
|
|
|
|
|
|
|
const auto* remote_description = handler_->remote_description();
|
|
|
|
|
|
|
|
|
|
const cricket::SessionDescription* session_desc =
|
|
|
|
|
remote_description->description();
|
|
|
|
|
|
|
|
|
|
// Transport and Media channels will be created only when offer is set.
|
|
|
|
|
if (unified_plan_) {
|
|
|
|
|
error_ = handler_->UpdateTransceiversAndDataChannels(
|
|
|
|
|
cricket::CS_REMOTE, *remote_description,
|
|
|
|
|
handler_->local_description(), old_remote_description(),
|
|
|
|
|
bundle_groups_by_mid_);
|
|
|
|
|
} else {
|
|
|
|
|
// Media channels will be created only when offer is set. These may use
|
|
|
|
|
// new transports just created by PushdownTransportDescription.
|
|
|
|
|
if (type_ == SdpType::kOffer) {
|
|
|
|
|
// TODO(mallinath) - Handle CreateChannel failure, as new local
|
|
|
|
|
// description is applied. Restore back to old description.
|
|
|
|
|
error_ = handler_->CreateChannels(*session_desc);
|
|
|
|
|
}
|
|
|
|
|
// Remove unused channels if MediaContentDescription is rejected.
|
|
|
|
|
handler_->RemoveUnusedChannels(session_desc);
|
2022-01-12 13:11:04 +01:00
|
|
|
}
|
|
|
|
|
|
2022-01-17 19:19:56 +01:00
|
|
|
return ok();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool UpdateSessionState() {
|
|
|
|
|
RTC_DCHECK(ok());
|
|
|
|
|
error_ = handler_->UpdateSessionState(
|
|
|
|
|
type_, cricket::CS_REMOTE,
|
|
|
|
|
handler_->remote_description()->description(), bundle_groups_by_mid_);
|
|
|
|
|
if (!ok())
|
|
|
|
|
SetAsSessionError();
|
|
|
|
|
return ok();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool UseCandidatesInRemoteDescription() {
|
|
|
|
|
RTC_DCHECK(ok());
|
|
|
|
|
if (handler_->local_description() &&
|
|
|
|
|
!handler_->UseCandidatesInRemoteDescription()) {
|
|
|
|
|
InvalidParam(kInvalidCandidates);
|
|
|
|
|
}
|
|
|
|
|
return ok();
|
2022-01-12 13:11:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convenience getter for desc_->GetType().
|
|
|
|
|
SdpType type() const { return type_; }
|
2022-01-17 19:19:56 +01:00
|
|
|
bool unified_plan() const { return unified_plan_; }
|
2022-01-12 13:11:04 +01:00
|
|
|
cricket::SessionDescription* description() { return desc_->description(); }
|
|
|
|
|
|
2022-01-17 19:19:56 +01:00
|
|
|
const SessionDescriptionInterface* old_remote_description() const {
|
|
|
|
|
RTC_DCHECK(!desc_) << "Called before replacing the remote description";
|
|
|
|
|
if (type_ == SdpType::kAnswer)
|
|
|
|
|
return replaced_remote_description_.get();
|
|
|
|
|
return replaced_remote_description_
|
|
|
|
|
? replaced_remote_description_.get()
|
|
|
|
|
: handler_->current_remote_description();
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-12 13:11:04 +01:00
|
|
|
// Returns a reference to a cached map of bundle groups ordered by mid.
|
|
|
|
|
// Note that this will only be valid after a successful call to
|
|
|
|
|
// `IsDescriptionValid`.
|
|
|
|
|
const std::map<std::string, const cricket::ContentGroup*>&
|
|
|
|
|
bundle_groups_by_mid() const {
|
|
|
|
|
RTC_DCHECK(ok());
|
|
|
|
|
return bundle_groups_by_mid_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
// Convenience methods for populating the embedded `error_` object.
|
|
|
|
|
void Unsupported(std::string message) {
|
|
|
|
|
SetError(RTCErrorType::UNSUPPORTED_OPERATION, std::move(message));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void InvalidParam(std::string message) {
|
|
|
|
|
SetError(RTCErrorType::INVALID_PARAMETER, std::move(message));
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-17 19:19:56 +01:00
|
|
|
void InternalError(std::string message) {
|
|
|
|
|
SetError(RTCErrorType::INTERNAL_ERROR, std::move(message));
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-12 13:11:04 +01:00
|
|
|
void SetError(RTCErrorType type, std::string message) {
|
|
|
|
|
RTC_DCHECK(ok()) << "Overwriting an existing error?";
|
|
|
|
|
error_ = RTCError(type, std::move(message));
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-17 19:19:56 +01:00
|
|
|
// Called when the PeerConnection could be in an inconsistent state and we set
|
|
|
|
|
// the session error so that future calls to
|
|
|
|
|
// SetLocalDescription/SetRemoteDescription fail.
|
|
|
|
|
void SetAsSessionError() {
|
|
|
|
|
RTC_DCHECK(!ok());
|
|
|
|
|
handler_->SetSessionError(SessionError::kContent, error_.message());
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-12 13:11:04 +01:00
|
|
|
SdpOfferAnswerHandler* const handler_;
|
|
|
|
|
std::unique_ptr<SessionDescriptionInterface> desc_;
|
2022-01-17 19:19:56 +01:00
|
|
|
// Keeps the replaced session description object alive while the operation
|
|
|
|
|
// is taking place since methods that depend on `old_remote_description()`
|
|
|
|
|
// for updating the state, need it.
|
|
|
|
|
std::unique_ptr<SessionDescriptionInterface> replaced_remote_description_;
|
2022-01-12 13:11:04 +01:00
|
|
|
rtc::scoped_refptr<SetRemoteDescriptionObserverInterface> observer_;
|
|
|
|
|
std::function<void()> operations_chain_callback_;
|
|
|
|
|
RTCError error_ = RTCError::OK();
|
|
|
|
|
std::map<std::string, const cricket::ContentGroup*> bundle_groups_by_mid_;
|
|
|
|
|
SdpType type_;
|
2022-01-17 19:19:56 +01:00
|
|
|
const bool unified_plan_;
|
2022-01-12 13:11:04 +01:00
|
|
|
};
|
2020-09-28 13:02:07 +00:00
|
|
|
// Used by parameterless SetLocalDescription() to create an offer or answer.
|
|
|
|
|
// Upon completion of creating the session description, SetLocalDescription() is
|
|
|
|
|
// invoked with the result.
|
|
|
|
|
class SdpOfferAnswerHandler::ImplicitCreateSessionDescriptionObserver
|
|
|
|
|
: public CreateSessionDescriptionObserver {
|
|
|
|
|
public:
|
|
|
|
|
ImplicitCreateSessionDescriptionObserver(
|
|
|
|
|
rtc::WeakPtr<SdpOfferAnswerHandler> sdp_handler,
|
|
|
|
|
rtc::scoped_refptr<SetLocalDescriptionObserverInterface>
|
|
|
|
|
set_local_description_observer)
|
|
|
|
|
: sdp_handler_(std::move(sdp_handler)),
|
|
|
|
|
set_local_description_observer_(
|
|
|
|
|
std::move(set_local_description_observer)) {}
|
|
|
|
|
~ImplicitCreateSessionDescriptionObserver() override {
|
|
|
|
|
RTC_DCHECK(was_called_);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SetOperationCompleteCallback(
|
|
|
|
|
std::function<void()> operation_complete_callback) {
|
|
|
|
|
operation_complete_callback_ = std::move(operation_complete_callback);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool was_called() const { return was_called_; }
|
|
|
|
|
|
|
|
|
|
void OnSuccess(SessionDescriptionInterface* desc_ptr) override {
|
|
|
|
|
RTC_DCHECK(!was_called_);
|
|
|
|
|
std::unique_ptr<SessionDescriptionInterface> desc(desc_ptr);
|
|
|
|
|
was_called_ = true;
|
|
|
|
|
|
2021-07-30 22:30:23 +02:00
|
|
|
// Abort early if `pc_` is no longer valid.
|
2020-09-28 13:02:07 +00:00
|
|
|
if (!sdp_handler_) {
|
|
|
|
|
operation_complete_callback_();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// DoSetLocalDescription() is a synchronous operation that invokes
|
2021-07-30 22:30:23 +02:00
|
|
|
// `set_local_description_observer_` with the result.
|
2020-09-28 13:02:07 +00:00
|
|
|
sdp_handler_->DoSetLocalDescription(
|
|
|
|
|
std::move(desc), std::move(set_local_description_observer_));
|
|
|
|
|
operation_complete_callback_();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OnFailure(RTCError error) override {
|
|
|
|
|
RTC_DCHECK(!was_called_);
|
|
|
|
|
was_called_ = true;
|
|
|
|
|
set_local_description_observer_->OnSetLocalDescriptionComplete(RTCError(
|
|
|
|
|
error.type(), std::string("SetLocalDescription failed to create "
|
|
|
|
|
"session description - ") +
|
|
|
|
|
error.message()));
|
|
|
|
|
operation_complete_callback_();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
bool was_called_ = false;
|
|
|
|
|
rtc::WeakPtr<SdpOfferAnswerHandler> sdp_handler_;
|
|
|
|
|
rtc::scoped_refptr<SetLocalDescriptionObserverInterface>
|
|
|
|
|
set_local_description_observer_;
|
|
|
|
|
std::function<void()> operation_complete_callback_;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Wraps a CreateSessionDescriptionObserver and an OperationsChain operation
|
|
|
|
|
// complete callback. When the observer is invoked, the wrapped observer is
|
|
|
|
|
// invoked followed by invoking the completion callback.
|
|
|
|
|
class CreateSessionDescriptionObserverOperationWrapper
|
|
|
|
|
: public CreateSessionDescriptionObserver {
|
|
|
|
|
public:
|
|
|
|
|
CreateSessionDescriptionObserverOperationWrapper(
|
|
|
|
|
rtc::scoped_refptr<CreateSessionDescriptionObserver> observer,
|
|
|
|
|
std::function<void()> operation_complete_callback)
|
|
|
|
|
: observer_(std::move(observer)),
|
|
|
|
|
operation_complete_callback_(std::move(operation_complete_callback)) {
|
|
|
|
|
RTC_DCHECK(observer_);
|
|
|
|
|
}
|
|
|
|
|
~CreateSessionDescriptionObserverOperationWrapper() override {
|
2020-10-05 21:41:36 +02:00
|
|
|
#if RTC_DCHECK_IS_ON
|
2020-09-28 13:02:07 +00:00
|
|
|
RTC_DCHECK(was_called_);
|
2020-10-05 21:41:36 +02:00
|
|
|
#endif
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OnSuccess(SessionDescriptionInterface* desc) override {
|
2020-10-05 21:41:36 +02:00
|
|
|
#if RTC_DCHECK_IS_ON
|
2020-09-28 13:02:07 +00:00
|
|
|
RTC_DCHECK(!was_called_);
|
|
|
|
|
was_called_ = true;
|
|
|
|
|
#endif // RTC_DCHECK_IS_ON
|
|
|
|
|
// Completing the operation before invoking the observer allows the observer
|
|
|
|
|
// to execute SetLocalDescription() without delay.
|
|
|
|
|
operation_complete_callback_();
|
|
|
|
|
observer_->OnSuccess(desc);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OnFailure(RTCError error) override {
|
2020-10-05 21:41:36 +02:00
|
|
|
#if RTC_DCHECK_IS_ON
|
2020-09-28 13:02:07 +00:00
|
|
|
RTC_DCHECK(!was_called_);
|
|
|
|
|
was_called_ = true;
|
|
|
|
|
#endif // RTC_DCHECK_IS_ON
|
|
|
|
|
operation_complete_callback_();
|
|
|
|
|
observer_->OnFailure(std::move(error));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
2020-10-05 21:41:36 +02:00
|
|
|
#if RTC_DCHECK_IS_ON
|
2020-09-28 13:02:07 +00:00
|
|
|
bool was_called_ = false;
|
|
|
|
|
#endif // RTC_DCHECK_IS_ON
|
|
|
|
|
rtc::scoped_refptr<CreateSessionDescriptionObserver> observer_;
|
|
|
|
|
std::function<void()> operation_complete_callback_;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Wrapper for SetSessionDescriptionObserver that invokes the success or failure
|
|
|
|
|
// callback in a posted message handled by the peer connection. This introduces
|
|
|
|
|
// a delay that prevents recursive API calls by the observer, but this also
|
|
|
|
|
// means that the PeerConnection can be modified before the observer sees the
|
|
|
|
|
// result of the operation. This is ill-advised for synchronizing states.
|
|
|
|
|
//
|
|
|
|
|
// Implements both the SetLocalDescriptionObserverInterface and the
|
|
|
|
|
// SetRemoteDescriptionObserverInterface.
|
|
|
|
|
class SdpOfferAnswerHandler::SetSessionDescriptionObserverAdapter
|
|
|
|
|
: public SetLocalDescriptionObserverInterface,
|
|
|
|
|
public SetRemoteDescriptionObserverInterface {
|
|
|
|
|
public:
|
|
|
|
|
SetSessionDescriptionObserverAdapter(
|
|
|
|
|
rtc::WeakPtr<SdpOfferAnswerHandler> handler,
|
|
|
|
|
rtc::scoped_refptr<SetSessionDescriptionObserver> inner_observer)
|
|
|
|
|
: handler_(std::move(handler)),
|
|
|
|
|
inner_observer_(std::move(inner_observer)) {}
|
|
|
|
|
|
|
|
|
|
// SetLocalDescriptionObserverInterface implementation.
|
|
|
|
|
void OnSetLocalDescriptionComplete(RTCError error) override {
|
|
|
|
|
OnSetDescriptionComplete(std::move(error));
|
|
|
|
|
}
|
|
|
|
|
// SetRemoteDescriptionObserverInterface implementation.
|
|
|
|
|
void OnSetRemoteDescriptionComplete(RTCError error) override {
|
|
|
|
|
OnSetDescriptionComplete(std::move(error));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
void OnSetDescriptionComplete(RTCError error) {
|
|
|
|
|
if (!handler_)
|
|
|
|
|
return;
|
|
|
|
|
if (error.ok()) {
|
2020-10-05 07:01:09 +00:00
|
|
|
handler_->pc_->message_handler()->PostSetSessionDescriptionSuccess(
|
2022-04-20 14:26:50 +02:00
|
|
|
inner_observer_.get());
|
2020-09-28 13:02:07 +00:00
|
|
|
} else {
|
2020-10-05 07:01:09 +00:00
|
|
|
handler_->pc_->message_handler()->PostSetSessionDescriptionFailure(
|
2022-04-20 14:26:50 +02:00
|
|
|
inner_observer_.get(), std::move(error));
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rtc::WeakPtr<SdpOfferAnswerHandler> handler_;
|
|
|
|
|
rtc::scoped_refptr<SetSessionDescriptionObserver> inner_observer_;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class SdpOfferAnswerHandler::LocalIceCredentialsToReplace {
|
|
|
|
|
public:
|
|
|
|
|
// Sets the ICE credentials that need restarting to the ICE credentials of
|
|
|
|
|
// the current and pending descriptions.
|
|
|
|
|
void SetIceCredentialsFromLocalDescriptions(
|
|
|
|
|
const SessionDescriptionInterface* current_local_description,
|
|
|
|
|
const SessionDescriptionInterface* pending_local_description) {
|
|
|
|
|
ice_credentials_.clear();
|
|
|
|
|
if (current_local_description) {
|
|
|
|
|
AppendIceCredentialsFromSessionDescription(*current_local_description);
|
|
|
|
|
}
|
|
|
|
|
if (pending_local_description) {
|
|
|
|
|
AppendIceCredentialsFromSessionDescription(*pending_local_description);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClearIceCredentials() { ice_credentials_.clear(); }
|
|
|
|
|
|
|
|
|
|
// Returns true if we have ICE credentials that need restarting.
|
|
|
|
|
bool HasIceCredentials() const { return !ice_credentials_.empty(); }
|
|
|
|
|
|
2021-07-30 22:30:23 +02:00
|
|
|
// Returns true if `local_description` shares no ICE credentials with the
|
2020-09-28 13:02:07 +00:00
|
|
|
// ICE credentials that need restarting.
|
|
|
|
|
bool SatisfiesIceRestart(
|
|
|
|
|
const SessionDescriptionInterface& local_description) const {
|
|
|
|
|
for (const auto& transport_info :
|
|
|
|
|
local_description.description()->transport_infos()) {
|
|
|
|
|
if (ice_credentials_.find(std::make_pair(
|
|
|
|
|
transport_info.description.ice_ufrag,
|
|
|
|
|
transport_info.description.ice_pwd)) != ice_credentials_.end()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
void AppendIceCredentialsFromSessionDescription(
|
|
|
|
|
const SessionDescriptionInterface& desc) {
|
|
|
|
|
for (const auto& transport_info : desc.description()->transport_infos()) {
|
|
|
|
|
ice_credentials_.insert(
|
|
|
|
|
std::make_pair(transport_info.description.ice_ufrag,
|
|
|
|
|
transport_info.description.ice_pwd));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::set<std::pair<std::string, std::string>> ice_credentials_;
|
|
|
|
|
};
|
|
|
|
|
|
2022-01-28 17:41:30 +00:00
|
|
|
SdpOfferAnswerHandler::SdpOfferAnswerHandler(PeerConnectionSdpMethods* pc,
|
|
|
|
|
ConnectionContext* context)
|
2020-09-28 13:02:07 +00:00
|
|
|
: pc_(pc),
|
2022-01-28 17:41:30 +00:00
|
|
|
context_(context),
|
2020-10-09 11:42:17 +00:00
|
|
|
local_streams_(StreamCollection::Create()),
|
|
|
|
|
remote_streams_(StreamCollection::Create()),
|
2020-09-28 13:02:07 +00:00
|
|
|
operations_chain_(rtc::OperationsChain::Create()),
|
2020-10-05 13:08:41 +00:00
|
|
|
rtcp_cname_(GenerateRtcpCname()),
|
2020-09-28 13:02:07 +00:00
|
|
|
local_ice_credentials_to_replace_(new LocalIceCredentialsToReplace()),
|
|
|
|
|
weak_ptr_factory_(this) {
|
|
|
|
|
operations_chain_->SetOnChainEmptyCallback(
|
|
|
|
|
[this_weak_ptr = weak_ptr_factory_.GetWeakPtr()]() {
|
|
|
|
|
if (!this_weak_ptr)
|
|
|
|
|
return;
|
|
|
|
|
this_weak_ptr->OnOperationsChainEmpty();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SdpOfferAnswerHandler::~SdpOfferAnswerHandler() {}
|
|
|
|
|
|
2020-10-27 07:10:43 +00:00
|
|
|
// Static
|
|
|
|
|
std::unique_ptr<SdpOfferAnswerHandler> SdpOfferAnswerHandler::Create(
|
2022-01-28 13:08:34 +00:00
|
|
|
PeerConnectionSdpMethods* pc,
|
2020-10-27 07:10:43 +00:00
|
|
|
const PeerConnectionInterface::RTCConfiguration& configuration,
|
2022-01-28 17:41:30 +00:00
|
|
|
PeerConnectionDependencies& dependencies,
|
2024-09-16 20:28:14 +00:00
|
|
|
ConnectionContext* context,
|
|
|
|
|
PayloadTypeSuggester* pt_suggester) {
|
2022-01-28 17:41:30 +00:00
|
|
|
auto handler = absl::WrapUnique(new SdpOfferAnswerHandler(pc, context));
|
2024-09-16 20:28:14 +00:00
|
|
|
handler->Initialize(configuration, dependencies, context, pt_suggester);
|
2020-10-27 07:10:43 +00:00
|
|
|
return handler;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-22 10:39:40 +00:00
|
|
|
void SdpOfferAnswerHandler::Initialize(
|
|
|
|
|
const PeerConnectionInterface::RTCConfiguration& configuration,
|
2022-03-09 09:28:10 +01:00
|
|
|
PeerConnectionDependencies& dependencies,
|
2024-09-16 20:28:14 +00:00
|
|
|
ConnectionContext* context,
|
|
|
|
|
PayloadTypeSuggester* pt_suggester) {
|
2020-10-22 10:39:40 +00:00
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
2022-05-10 09:34:27 +02:00
|
|
|
// 100 kbps is used by default, but can be overriden by a non-standard
|
|
|
|
|
// RTCConfiguration value (not available on Web).
|
2020-10-22 10:39:40 +00:00
|
|
|
video_options_.screencast_min_bitrate_kbps =
|
2022-05-10 09:34:27 +02:00
|
|
|
configuration.screencast_min_bitrate.value_or(100);
|
2020-10-22 10:39:40 +00:00
|
|
|
|
|
|
|
|
audio_options_.audio_jitter_buffer_max_packets =
|
|
|
|
|
configuration.audio_jitter_buffer_max_packets;
|
|
|
|
|
|
|
|
|
|
audio_options_.audio_jitter_buffer_fast_accelerate =
|
|
|
|
|
configuration.audio_jitter_buffer_fast_accelerate;
|
|
|
|
|
|
|
|
|
|
audio_options_.audio_jitter_buffer_min_delay_ms =
|
|
|
|
|
configuration.audio_jitter_buffer_min_delay_ms;
|
|
|
|
|
|
|
|
|
|
// Obtain a certificate from RTCConfiguration if any were provided (optional).
|
|
|
|
|
rtc::scoped_refptr<rtc::RTCCertificate> certificate;
|
|
|
|
|
if (!configuration.certificates.empty()) {
|
|
|
|
|
// TODO(hbos,torbjorng): Decide on certificate-selection strategy instead of
|
|
|
|
|
// just picking the first one. The decision should be made based on the DTLS
|
|
|
|
|
// handshake. The DTLS negotiations need to know about all certificates.
|
|
|
|
|
certificate = configuration.certificates[0];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
webrtc_session_desc_factory_ =
|
|
|
|
|
std::make_unique<WebRtcSessionDescriptionFactory>(
|
2022-03-09 09:28:10 +01:00
|
|
|
context, this, pc_->session_id(), pc_->dtls_enabled(),
|
2022-08-22 16:39:34 +02:00
|
|
|
std::move(dependencies.cert_generator), std::move(certificate),
|
2020-11-09 14:15:00 +00:00
|
|
|
[this](const rtc::scoped_refptr<rtc::RTCCertificate>& certificate) {
|
2022-02-09 12:08:47 +00:00
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
transport_controller_s()->SetLocalCertificate(certificate);
|
2022-04-19 17:24:10 +02:00
|
|
|
},
|
2024-09-16 20:28:14 +00:00
|
|
|
pt_suggester, pc_->trials());
|
2020-10-22 10:39:40 +00:00
|
|
|
|
2021-11-04 13:52:31 +00:00
|
|
|
if (pc_->options()->disable_encryption) {
|
2024-02-08 13:15:51 +00:00
|
|
|
RTC_LOG(LS_INFO)
|
|
|
|
|
<< "Disabling encryption. This should only be done in tests.";
|
|
|
|
|
webrtc_session_desc_factory_->SetInsecureForTesting();
|
2021-11-04 13:52:31 +00:00
|
|
|
}
|
|
|
|
|
|
2020-10-22 10:39:40 +00:00
|
|
|
webrtc_session_desc_factory_->set_enable_encrypted_rtp_header_extensions(
|
|
|
|
|
pc_->GetCryptoOptions().srtp.enable_encrypted_rtp_header_extensions);
|
|
|
|
|
webrtc_session_desc_factory_->set_is_unified_plan(IsUnifiedPlan());
|
|
|
|
|
|
2020-10-27 07:10:43 +00:00
|
|
|
if (dependencies.video_bitrate_allocator_factory) {
|
2020-10-22 10:39:40 +00:00
|
|
|
video_bitrate_allocator_factory_ =
|
2020-10-27 07:10:43 +00:00
|
|
|
std::move(dependencies.video_bitrate_allocator_factory);
|
2020-10-22 10:39:40 +00:00
|
|
|
} else {
|
|
|
|
|
video_bitrate_allocator_factory_ =
|
|
|
|
|
CreateBuiltinVideoBitrateAllocatorFactory();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-05 13:08:41 +00:00
|
|
|
// ==================================================================
|
2022-05-09 08:30:09 +00:00
|
|
|
// Access to pc_ variables
|
2022-05-16 10:36:43 +00:00
|
|
|
cricket::MediaEngineInterface* SdpOfferAnswerHandler::media_engine() const {
|
|
|
|
|
RTC_DCHECK(context_);
|
2022-05-22 10:57:01 +00:00
|
|
|
return context_->media_engine();
|
2022-05-16 10:36:43 +00:00
|
|
|
}
|
|
|
|
|
|
2020-10-19 13:28:05 +00:00
|
|
|
TransceiverList* SdpOfferAnswerHandler::transceivers() {
|
|
|
|
|
if (!pc_->rtp_manager()) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
return pc_->rtp_manager()->transceivers();
|
2020-10-05 13:08:41 +00:00
|
|
|
}
|
2022-05-16 10:36:43 +00:00
|
|
|
|
2020-10-19 13:28:05 +00:00
|
|
|
const TransceiverList* SdpOfferAnswerHandler::transceivers() const {
|
|
|
|
|
if (!pc_->rtp_manager()) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
return pc_->rtp_manager()->transceivers();
|
2020-10-05 13:08:41 +00:00
|
|
|
}
|
2022-02-09 12:08:47 +00:00
|
|
|
JsepTransportController* SdpOfferAnswerHandler::transport_controller_s() {
|
|
|
|
|
return pc_->transport_controller_s();
|
2020-10-05 13:08:41 +00:00
|
|
|
}
|
2022-02-09 12:08:47 +00:00
|
|
|
JsepTransportController* SdpOfferAnswerHandler::transport_controller_n() {
|
|
|
|
|
return pc_->transport_controller_n();
|
|
|
|
|
}
|
|
|
|
|
const JsepTransportController* SdpOfferAnswerHandler::transport_controller_s()
|
|
|
|
|
const {
|
|
|
|
|
return pc_->transport_controller_s();
|
|
|
|
|
}
|
|
|
|
|
const JsepTransportController* SdpOfferAnswerHandler::transport_controller_n()
|
2020-10-23 13:30:46 +00:00
|
|
|
const {
|
2022-02-09 12:08:47 +00:00
|
|
|
return pc_->transport_controller_n();
|
2020-10-23 13:30:46 +00:00
|
|
|
}
|
2020-10-05 13:08:41 +00:00
|
|
|
DataChannelController* SdpOfferAnswerHandler::data_channel_controller() {
|
2020-10-19 16:05:20 +00:00
|
|
|
return pc_->data_channel_controller();
|
2020-10-05 13:08:41 +00:00
|
|
|
}
|
|
|
|
|
const DataChannelController* SdpOfferAnswerHandler::data_channel_controller()
|
|
|
|
|
const {
|
2020-10-19 16:05:20 +00:00
|
|
|
return pc_->data_channel_controller();
|
2020-10-05 13:08:41 +00:00
|
|
|
}
|
|
|
|
|
cricket::PortAllocator* SdpOfferAnswerHandler::port_allocator() {
|
2020-10-19 16:05:20 +00:00
|
|
|
return pc_->port_allocator();
|
2020-10-05 13:08:41 +00:00
|
|
|
}
|
|
|
|
|
const cricket::PortAllocator* SdpOfferAnswerHandler::port_allocator() const {
|
2020-10-19 16:05:20 +00:00
|
|
|
return pc_->port_allocator();
|
2020-10-05 13:08:41 +00:00
|
|
|
}
|
2020-10-19 13:28:05 +00:00
|
|
|
RtpTransmissionManager* SdpOfferAnswerHandler::rtp_manager() {
|
|
|
|
|
return pc_->rtp_manager();
|
|
|
|
|
}
|
|
|
|
|
const RtpTransmissionManager* SdpOfferAnswerHandler::rtp_manager() const {
|
|
|
|
|
return pc_->rtp_manager();
|
|
|
|
|
}
|
2020-10-05 13:08:41 +00:00
|
|
|
|
|
|
|
|
// ===================================================================
|
|
|
|
|
|
2020-09-28 13:02:07 +00:00
|
|
|
void SdpOfferAnswerHandler::PrepareForShutdown() {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
weak_ptr_factory_.InvalidateWeakPtrs();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::Close() {
|
|
|
|
|
ChangeSignalingState(PeerConnectionInterface::kClosed);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::RestartIce() {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
local_ice_credentials_to_replace_->SetIceCredentialsFromLocalDescriptions(
|
|
|
|
|
current_local_description(), pending_local_description());
|
|
|
|
|
UpdateNegotiationNeeded();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rtc::Thread* SdpOfferAnswerHandler::signaling_thread() const {
|
2022-01-28 17:41:30 +00:00
|
|
|
return context_->signaling_thread();
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
|
2022-02-09 12:08:47 +00:00
|
|
|
rtc::Thread* SdpOfferAnswerHandler::network_thread() const {
|
|
|
|
|
return context_->network_thread();
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-28 13:02:07 +00:00
|
|
|
void SdpOfferAnswerHandler::CreateOffer(
|
|
|
|
|
CreateSessionDescriptionObserver* observer,
|
|
|
|
|
const PeerConnectionInterface::RTCOfferAnswerOptions& options) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
// Chain this operation. If asynchronous operations are pending on the chain,
|
|
|
|
|
// this operation will be queued to be invoked, otherwise the contents of the
|
|
|
|
|
// lambda will execute immediately.
|
|
|
|
|
operations_chain_->ChainOperation(
|
|
|
|
|
[this_weak_ptr = weak_ptr_factory_.GetWeakPtr(),
|
|
|
|
|
observer_refptr =
|
|
|
|
|
rtc::scoped_refptr<CreateSessionDescriptionObserver>(observer),
|
|
|
|
|
options](std::function<void()> operations_chain_callback) {
|
2021-07-30 22:30:23 +02:00
|
|
|
// Abort early if `this_weak_ptr` is no longer valid.
|
2020-09-28 13:02:07 +00:00
|
|
|
if (!this_weak_ptr) {
|
|
|
|
|
observer_refptr->OnFailure(
|
|
|
|
|
RTCError(RTCErrorType::INTERNAL_ERROR,
|
|
|
|
|
"CreateOffer failed because the session was shut down"));
|
|
|
|
|
operations_chain_callback();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// The operation completes asynchronously when the wrapper is invoked.
|
2021-08-23 15:48:06 +02:00
|
|
|
auto observer_wrapper = rtc::make_ref_counted<
|
|
|
|
|
CreateSessionDescriptionObserverOperationWrapper>(
|
|
|
|
|
std::move(observer_refptr), std::move(operations_chain_callback));
|
2020-09-28 13:02:07 +00:00
|
|
|
this_weak_ptr->DoCreateOffer(options, observer_wrapper);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::SetLocalDescription(
|
|
|
|
|
SetSessionDescriptionObserver* observer,
|
|
|
|
|
SessionDescriptionInterface* desc_ptr) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
// Chain this operation. If asynchronous operations are pending on the chain,
|
|
|
|
|
// this operation will be queued to be invoked, otherwise the contents of the
|
|
|
|
|
// lambda will execute immediately.
|
|
|
|
|
operations_chain_->ChainOperation(
|
|
|
|
|
[this_weak_ptr = weak_ptr_factory_.GetWeakPtr(),
|
|
|
|
|
observer_refptr =
|
|
|
|
|
rtc::scoped_refptr<SetSessionDescriptionObserver>(observer),
|
|
|
|
|
desc = std::unique_ptr<SessionDescriptionInterface>(desc_ptr)](
|
|
|
|
|
std::function<void()> operations_chain_callback) mutable {
|
2021-07-30 22:30:23 +02:00
|
|
|
// Abort early if `this_weak_ptr` is no longer valid.
|
2020-09-28 13:02:07 +00:00
|
|
|
if (!this_weak_ptr) {
|
|
|
|
|
// For consistency with SetSessionDescriptionObserverAdapter whose
|
|
|
|
|
// posted messages doesn't get processed when the PC is destroyed, we
|
2021-07-30 22:30:23 +02:00
|
|
|
// do not inform `observer_refptr` that the operation failed.
|
2020-09-28 13:02:07 +00:00
|
|
|
operations_chain_callback();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// SetSessionDescriptionObserverAdapter takes care of making sure the
|
2021-07-30 22:30:23 +02:00
|
|
|
// `observer_refptr` is invoked in a posted message.
|
2020-09-28 13:02:07 +00:00
|
|
|
this_weak_ptr->DoSetLocalDescription(
|
|
|
|
|
std::move(desc),
|
2021-08-23 15:48:06 +02:00
|
|
|
rtc::make_ref_counted<SetSessionDescriptionObserverAdapter>(
|
|
|
|
|
this_weak_ptr, observer_refptr));
|
2020-09-28 13:02:07 +00:00
|
|
|
// For backwards-compatability reasons, we declare the operation as
|
|
|
|
|
// completed here (rather than in a post), so that the operation chain
|
|
|
|
|
// is not blocked by this operation when the observer is invoked. This
|
|
|
|
|
// allows the observer to trigger subsequent offer/answer operations
|
|
|
|
|
// synchronously if the operation chain is now empty.
|
|
|
|
|
operations_chain_callback();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::SetLocalDescription(
|
|
|
|
|
std::unique_ptr<SessionDescriptionInterface> desc,
|
|
|
|
|
rtc::scoped_refptr<SetLocalDescriptionObserverInterface> observer) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
// Chain this operation. If asynchronous operations are pending on the chain,
|
|
|
|
|
// this operation will be queued to be invoked, otherwise the contents of the
|
|
|
|
|
// lambda will execute immediately.
|
|
|
|
|
operations_chain_->ChainOperation(
|
|
|
|
|
[this_weak_ptr = weak_ptr_factory_.GetWeakPtr(), observer,
|
|
|
|
|
desc = std::move(desc)](
|
|
|
|
|
std::function<void()> operations_chain_callback) mutable {
|
2021-07-30 22:30:23 +02:00
|
|
|
// Abort early if `this_weak_ptr` is no longer valid.
|
2020-09-28 13:02:07 +00:00
|
|
|
if (!this_weak_ptr) {
|
|
|
|
|
observer->OnSetLocalDescriptionComplete(RTCError(
|
|
|
|
|
RTCErrorType::INTERNAL_ERROR,
|
|
|
|
|
"SetLocalDescription failed because the session was shut down"));
|
|
|
|
|
operations_chain_callback();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this_weak_ptr->DoSetLocalDescription(std::move(desc), observer);
|
|
|
|
|
// DoSetLocalDescription() is implemented as a synchronous operation.
|
2021-07-30 22:30:23 +02:00
|
|
|
// The `observer` will already have been informed that it completed, and
|
2020-09-28 13:02:07 +00:00
|
|
|
// we can mark this operation as complete without any loose ends.
|
|
|
|
|
operations_chain_callback();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::SetLocalDescription(
|
|
|
|
|
SetSessionDescriptionObserver* observer) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
SetLocalDescription(
|
2021-08-23 15:48:06 +02:00
|
|
|
rtc::make_ref_counted<SetSessionDescriptionObserverAdapter>(
|
2022-01-04 15:20:03 +01:00
|
|
|
weak_ptr_factory_.GetWeakPtr(),
|
|
|
|
|
rtc::scoped_refptr<SetSessionDescriptionObserver>(observer)));
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::SetLocalDescription(
|
|
|
|
|
rtc::scoped_refptr<SetLocalDescriptionObserverInterface> observer) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
2021-07-30 22:30:23 +02:00
|
|
|
// The `create_sdp_observer` handles performing DoSetLocalDescription() with
|
2020-09-28 13:02:07 +00:00
|
|
|
// the resulting description as well as completing the operation.
|
2021-08-23 15:48:06 +02:00
|
|
|
auto create_sdp_observer =
|
|
|
|
|
rtc::make_ref_counted<ImplicitCreateSessionDescriptionObserver>(
|
|
|
|
|
weak_ptr_factory_.GetWeakPtr(), observer);
|
2020-09-28 13:02:07 +00:00
|
|
|
// Chain this operation. If asynchronous operations are pending on the chain,
|
|
|
|
|
// this operation will be queued to be invoked, otherwise the contents of the
|
|
|
|
|
// lambda will execute immediately.
|
|
|
|
|
operations_chain_->ChainOperation(
|
|
|
|
|
[this_weak_ptr = weak_ptr_factory_.GetWeakPtr(),
|
|
|
|
|
create_sdp_observer](std::function<void()> operations_chain_callback) {
|
2021-07-30 22:30:23 +02:00
|
|
|
// The `create_sdp_observer` is responsible for completing the
|
2020-09-28 13:02:07 +00:00
|
|
|
// operation.
|
|
|
|
|
create_sdp_observer->SetOperationCompleteCallback(
|
|
|
|
|
std::move(operations_chain_callback));
|
2021-07-30 22:30:23 +02:00
|
|
|
// Abort early if `this_weak_ptr` is no longer valid. This triggers the
|
2020-09-28 13:02:07 +00:00
|
|
|
// same code path as if DoCreateOffer() or DoCreateAnswer() failed.
|
|
|
|
|
if (!this_weak_ptr) {
|
|
|
|
|
create_sdp_observer->OnFailure(RTCError(
|
|
|
|
|
RTCErrorType::INTERNAL_ERROR,
|
|
|
|
|
"SetLocalDescription failed because the session was shut down"));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
switch (this_weak_ptr->signaling_state()) {
|
|
|
|
|
case PeerConnectionInterface::kStable:
|
|
|
|
|
case PeerConnectionInterface::kHaveLocalOffer:
|
|
|
|
|
case PeerConnectionInterface::kHaveRemotePrAnswer:
|
|
|
|
|
// TODO(hbos): If [LastCreatedOffer] exists and still represents the
|
|
|
|
|
// current state of the system, use that instead of creating another
|
|
|
|
|
// offer.
|
|
|
|
|
this_weak_ptr->DoCreateOffer(
|
|
|
|
|
PeerConnectionInterface::RTCOfferAnswerOptions(),
|
|
|
|
|
create_sdp_observer);
|
|
|
|
|
break;
|
|
|
|
|
case PeerConnectionInterface::kHaveLocalPrAnswer:
|
|
|
|
|
case PeerConnectionInterface::kHaveRemoteOffer:
|
|
|
|
|
// TODO(hbos): If [LastCreatedAnswer] exists and still represents
|
|
|
|
|
// the current state of the system, use that instead of creating
|
|
|
|
|
// another answer.
|
|
|
|
|
this_weak_ptr->DoCreateAnswer(
|
|
|
|
|
PeerConnectionInterface::RTCOfferAnswerOptions(),
|
|
|
|
|
create_sdp_observer);
|
|
|
|
|
break;
|
|
|
|
|
case PeerConnectionInterface::kClosed:
|
|
|
|
|
create_sdp_observer->OnFailure(RTCError(
|
|
|
|
|
RTCErrorType::INVALID_STATE,
|
|
|
|
|
"SetLocalDescription called when PeerConnection is closed."));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RTCError SdpOfferAnswerHandler::ApplyLocalDescription(
|
2021-04-26 21:04:26 +02:00
|
|
|
std::unique_ptr<SessionDescriptionInterface> desc,
|
|
|
|
|
const std::map<std::string, const cricket::ContentGroup*>&
|
|
|
|
|
bundle_groups_by_mid) {
|
2021-06-07 13:30:46 +02:00
|
|
|
TRACE_EVENT0("webrtc", "SdpOfferAnswerHandler::ApplyLocalDescription");
|
2020-09-28 13:02:07 +00:00
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
RTC_DCHECK(desc);
|
|
|
|
|
|
2022-09-01 15:39:50 +02:00
|
|
|
// Invalidate the stats caches to make sure that they get
|
|
|
|
|
// updated the next time getStats() gets called, as updating the session
|
|
|
|
|
// description affects the stats.
|
|
|
|
|
pc_->ClearStatsCache();
|
2020-09-28 13:02:07 +00:00
|
|
|
|
|
|
|
|
// Take a reference to the old local description since it's used below to
|
|
|
|
|
// compare against the new local description. When setting the new local
|
|
|
|
|
// description, grab ownership of the replaced session description in case it
|
2021-07-30 22:30:23 +02:00
|
|
|
// is the same as `old_local_description`, to keep it alive for the duration
|
2020-09-28 13:02:07 +00:00
|
|
|
// of the method.
|
|
|
|
|
const SessionDescriptionInterface* old_local_description =
|
|
|
|
|
local_description();
|
|
|
|
|
std::unique_ptr<SessionDescriptionInterface> replaced_local_description;
|
|
|
|
|
SdpType type = desc->GetType();
|
|
|
|
|
if (type == SdpType::kAnswer) {
|
|
|
|
|
replaced_local_description = pending_local_description_
|
|
|
|
|
? std::move(pending_local_description_)
|
|
|
|
|
: std::move(current_local_description_);
|
|
|
|
|
current_local_description_ = std::move(desc);
|
|
|
|
|
pending_local_description_ = nullptr;
|
|
|
|
|
current_remote_description_ = std::move(pending_remote_description_);
|
|
|
|
|
} else {
|
|
|
|
|
replaced_local_description = std::move(pending_local_description_);
|
|
|
|
|
pending_local_description_ = std::move(desc);
|
|
|
|
|
}
|
2022-02-09 12:08:47 +00:00
|
|
|
if (!initial_offerer_) {
|
|
|
|
|
initial_offerer_.emplace(type == SdpType::kOffer);
|
|
|
|
|
}
|
2020-09-28 13:02:07 +00:00
|
|
|
// The session description to apply now must be accessed by
|
2021-07-30 22:30:23 +02:00
|
|
|
// `local_description()`.
|
2020-09-28 13:02:07 +00:00
|
|
|
RTC_DCHECK(local_description());
|
|
|
|
|
|
|
|
|
|
if (!is_caller_) {
|
|
|
|
|
if (remote_description()) {
|
|
|
|
|
// Remote description was applied first, so this PC is the callee.
|
|
|
|
|
is_caller_ = false;
|
|
|
|
|
} else {
|
|
|
|
|
// Local description is applied first, so this PC is the caller.
|
|
|
|
|
is_caller_ = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-01 16:47:23 +00:00
|
|
|
RTCError error = PushdownTransportDescription(cricket::CS_LOCAL, type);
|
2020-09-28 13:02:07 +00:00
|
|
|
if (!error.ok()) {
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (IsUnifiedPlan()) {
|
2022-01-17 19:19:56 +01:00
|
|
|
error = UpdateTransceiversAndDataChannels(
|
2020-09-28 13:02:07 +00:00
|
|
|
cricket::CS_LOCAL, *local_description(), old_local_description,
|
2021-04-26 21:04:26 +02:00
|
|
|
remote_description(), bundle_groups_by_mid);
|
2020-09-28 13:02:07 +00:00
|
|
|
if (!error.ok()) {
|
2022-01-17 19:19:56 +01:00
|
|
|
RTC_LOG(LS_ERROR) << error.message() << " (" << SdpTypeToString(type)
|
|
|
|
|
<< ")";
|
2020-09-28 13:02:07 +00:00
|
|
|
return error;
|
|
|
|
|
}
|
2022-05-23 14:57:47 +00:00
|
|
|
if (ConfiguredForMedia()) {
|
|
|
|
|
std::vector<rtc::scoped_refptr<RtpTransceiverInterface>> remove_list;
|
|
|
|
|
std::vector<rtc::scoped_refptr<MediaStreamInterface>> removed_streams;
|
|
|
|
|
for (const auto& transceiver_ext : transceivers()->List()) {
|
|
|
|
|
auto transceiver = transceiver_ext->internal();
|
|
|
|
|
if (transceiver->stopped()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2020-09-28 13:02:07 +00:00
|
|
|
|
2022-05-23 14:57:47 +00:00
|
|
|
// 2.2.7.1.1.(6-9): Set sender and receiver's transport slots.
|
|
|
|
|
// Note that code paths that don't set MID won't be able to use
|
|
|
|
|
// information about DTLS transports.
|
|
|
|
|
if (transceiver->mid()) {
|
|
|
|
|
auto dtls_transport = LookupDtlsTransportByMid(
|
|
|
|
|
context_->network_thread(), transport_controller_s(),
|
|
|
|
|
*transceiver->mid());
|
|
|
|
|
transceiver->sender_internal()->set_transport(dtls_transport);
|
|
|
|
|
transceiver->receiver_internal()->set_transport(dtls_transport);
|
|
|
|
|
}
|
2022-05-09 08:30:09 +00:00
|
|
|
|
2022-05-23 14:57:47 +00:00
|
|
|
const ContentInfo* content =
|
|
|
|
|
FindMediaSectionForTransceiver(transceiver, local_description());
|
|
|
|
|
if (!content) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const MediaContentDescription* media_desc =
|
|
|
|
|
content->media_description();
|
|
|
|
|
// 2.2.7.1.6: If description is of type "answer" or "pranswer", then run
|
|
|
|
|
// the following steps:
|
|
|
|
|
if (type == SdpType::kPrAnswer || type == SdpType::kAnswer) {
|
|
|
|
|
// 2.2.7.1.6.1: If direction is "sendonly" or "inactive", and
|
|
|
|
|
// transceiver's [[FiredDirection]] slot is either "sendrecv" or
|
|
|
|
|
// "recvonly", process the removal of a remote track for the media
|
|
|
|
|
// description, given transceiver, removeList, and muteTracks.
|
|
|
|
|
if (!RtpTransceiverDirectionHasRecv(media_desc->direction()) &&
|
|
|
|
|
(transceiver->fired_direction() &&
|
|
|
|
|
RtpTransceiverDirectionHasRecv(
|
|
|
|
|
*transceiver->fired_direction()))) {
|
|
|
|
|
ProcessRemovalOfRemoteTrack(transceiver_ext, &remove_list,
|
|
|
|
|
&removed_streams);
|
|
|
|
|
}
|
|
|
|
|
// 2.2.7.1.6.2: Set transceiver's [[CurrentDirection]] and
|
|
|
|
|
// [[FiredDirection]] slots to direction.
|
|
|
|
|
transceiver->set_current_direction(media_desc->direction());
|
|
|
|
|
transceiver->set_fired_direction(media_desc->direction());
|
2022-05-09 08:30:09 +00:00
|
|
|
}
|
2022-05-06 15:15:34 +00:00
|
|
|
}
|
2022-05-23 14:57:47 +00:00
|
|
|
auto observer = pc_->Observer();
|
|
|
|
|
for (const auto& transceiver : remove_list) {
|
|
|
|
|
observer->OnRemoveTrack(transceiver->receiver());
|
|
|
|
|
}
|
|
|
|
|
for (const auto& stream : removed_streams) {
|
|
|
|
|
observer->OnRemoveStream(stream);
|
|
|
|
|
}
|
2022-05-09 08:30:09 +00:00
|
|
|
}
|
2020-09-28 13:02:07 +00:00
|
|
|
} else {
|
|
|
|
|
// Media channels will be created only when offer is set. These may use new
|
|
|
|
|
// transports just created by PushdownTransportDescription.
|
|
|
|
|
if (type == SdpType::kOffer) {
|
|
|
|
|
// TODO(bugs.webrtc.org/4676) - Handle CreateChannel failure, as new local
|
|
|
|
|
// description is applied. Restore back to old description.
|
2023-07-18 07:23:02 +02:00
|
|
|
error = CreateChannels(*local_description()->description());
|
2020-09-28 13:02:07 +00:00
|
|
|
if (!error.ok()) {
|
2022-01-17 19:19:56 +01:00
|
|
|
RTC_LOG(LS_ERROR) << error.message() << " (" << SdpTypeToString(type)
|
|
|
|
|
<< ")";
|
2020-09-28 13:02:07 +00:00
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Remove unused channels if MediaContentDescription is rejected.
|
2020-10-01 16:47:23 +00:00
|
|
|
RemoveUnusedChannels(local_description()->description());
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
error = UpdateSessionState(type, cricket::CS_LOCAL,
|
2021-04-26 21:04:26 +02:00
|
|
|
local_description()->description(),
|
|
|
|
|
bundle_groups_by_mid);
|
2020-09-28 13:02:07 +00:00
|
|
|
if (!error.ok()) {
|
2022-01-17 19:19:56 +01:00
|
|
|
RTC_LOG(LS_ERROR) << error.message() << " (" << SdpTypeToString(type)
|
|
|
|
|
<< ")";
|
2020-09-28 13:02:07 +00:00
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-17 19:19:56 +01:00
|
|
|
// Now that we have a local description, we can push down remote candidates.
|
|
|
|
|
UseCandidatesInRemoteDescription();
|
2020-09-28 13:02:07 +00:00
|
|
|
|
|
|
|
|
pending_ice_restarts_.clear();
|
2020-10-01 16:47:23 +00:00
|
|
|
if (session_error() != SessionError::kNone) {
|
|
|
|
|
LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR, GetSessionErrorMsg());
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If setting the description decided our SSL role, allocate any necessary
|
|
|
|
|
// SCTP sids.
|
2023-03-22 08:25:38 +01:00
|
|
|
AllocateSctpSids();
|
2020-09-28 13:02:07 +00:00
|
|
|
|
2023-07-18 07:23:02 +02:00
|
|
|
// Validate SSRCs, we do not allow duplicates.
|
|
|
|
|
if (ConfiguredForMedia()) {
|
|
|
|
|
std::set<uint32_t> used_ssrcs;
|
|
|
|
|
for (const auto& content : local_description()->description()->contents()) {
|
|
|
|
|
for (const auto& stream : content.media_description()->streams()) {
|
|
|
|
|
for (uint32_t ssrc : stream.ssrcs) {
|
|
|
|
|
auto result = used_ssrcs.insert(ssrc);
|
|
|
|
|
if (!result.second) {
|
|
|
|
|
LOG_AND_RETURN_ERROR(
|
|
|
|
|
RTCErrorType::INVALID_PARAMETER,
|
|
|
|
|
"Duplicate ssrc " + rtc::ToString(ssrc) + " is not allowed");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-28 13:02:07 +00:00
|
|
|
if (IsUnifiedPlan()) {
|
2022-05-23 14:57:47 +00:00
|
|
|
if (ConfiguredForMedia()) {
|
|
|
|
|
// We must use List and not ListInternal here because
|
|
|
|
|
// transceivers()->StableState() is indexed by the non-internal refptr.
|
|
|
|
|
for (const auto& transceiver_ext : transceivers()->List()) {
|
|
|
|
|
auto transceiver = transceiver_ext->internal();
|
|
|
|
|
if (transceiver->stopped()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const ContentInfo* content =
|
|
|
|
|
FindMediaSectionForTransceiver(transceiver, local_description());
|
|
|
|
|
if (!content) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
cricket::ChannelInterface* channel = transceiver->channel();
|
|
|
|
|
if (content->rejected || !channel || channel->local_streams().empty()) {
|
|
|
|
|
// 0 is a special value meaning "this sender has no associated send
|
|
|
|
|
// stream". Need to call this so the sender won't attempt to configure
|
|
|
|
|
// a no longer existing stream and run into DCHECKs in the lower
|
|
|
|
|
// layers.
|
|
|
|
|
transceiver->sender_internal()->SetSsrc(0);
|
|
|
|
|
} else {
|
|
|
|
|
// Get the StreamParams from the channel which could generate SSRCs.
|
|
|
|
|
const std::vector<StreamParams>& streams = channel->local_streams();
|
|
|
|
|
transceiver->sender_internal()->set_stream_ids(
|
|
|
|
|
streams[0].stream_ids());
|
|
|
|
|
auto encodings =
|
|
|
|
|
transceiver->sender_internal()->init_send_encodings();
|
|
|
|
|
transceiver->sender_internal()->SetSsrc(streams[0].first_ssrc());
|
|
|
|
|
if (!encodings.empty()) {
|
|
|
|
|
transceivers()
|
|
|
|
|
->StableState(transceiver_ext)
|
|
|
|
|
->SetInitSendEncodings(encodings);
|
|
|
|
|
}
|
2021-04-06 22:38:00 +03:00
|
|
|
}
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Plan B semantics.
|
|
|
|
|
|
|
|
|
|
// Update state and SSRC of local MediaStreams and DataChannels based on the
|
|
|
|
|
// local session description.
|
|
|
|
|
const cricket::ContentInfo* audio_content =
|
|
|
|
|
GetFirstAudioContent(local_description()->description());
|
|
|
|
|
if (audio_content) {
|
|
|
|
|
if (audio_content->rejected) {
|
2020-10-01 16:47:23 +00:00
|
|
|
RemoveSenders(cricket::MEDIA_TYPE_AUDIO);
|
2020-09-28 13:02:07 +00:00
|
|
|
} else {
|
2023-11-23 20:21:05 +01:00
|
|
|
const cricket::MediaContentDescription* audio_desc =
|
|
|
|
|
audio_content->media_description();
|
2020-10-01 16:47:23 +00:00
|
|
|
UpdateLocalSenders(audio_desc->streams(), audio_desc->type());
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const cricket::ContentInfo* video_content =
|
|
|
|
|
GetFirstVideoContent(local_description()->description());
|
|
|
|
|
if (video_content) {
|
|
|
|
|
if (video_content->rejected) {
|
2020-10-01 16:47:23 +00:00
|
|
|
RemoveSenders(cricket::MEDIA_TYPE_VIDEO);
|
2020-09-28 13:02:07 +00:00
|
|
|
} else {
|
2023-11-23 20:21:05 +01:00
|
|
|
const cricket::MediaContentDescription* video_desc =
|
|
|
|
|
video_content->media_description();
|
2020-10-01 16:47:23 +00:00
|
|
|
UpdateLocalSenders(video_desc->streams(), video_desc->type());
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-16 11:12:14 +00:00
|
|
|
// This function does nothing with data content.
|
2020-09-28 13:02:07 +00:00
|
|
|
|
|
|
|
|
if (type == SdpType::kAnswer &&
|
|
|
|
|
local_ice_credentials_to_replace_->SatisfiesIceRestart(
|
|
|
|
|
*current_local_description_)) {
|
|
|
|
|
local_ice_credentials_to_replace_->ClearIceCredentials();
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-30 13:51:39 +02:00
|
|
|
if (IsUnifiedPlan()) {
|
|
|
|
|
UpdateRtpHeaderExtensionPreferencesFromSdpMunging(
|
|
|
|
|
local_description()->description(), transceivers());
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-28 13:02:07 +00:00
|
|
|
return RTCError::OK();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::SetRemoteDescription(
|
|
|
|
|
SetSessionDescriptionObserver* observer,
|
|
|
|
|
SessionDescriptionInterface* desc_ptr) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
// Chain this operation. If asynchronous operations are pending on the chain,
|
|
|
|
|
// this operation will be queued to be invoked, otherwise the contents of the
|
|
|
|
|
// lambda will execute immediately.
|
|
|
|
|
operations_chain_->ChainOperation(
|
|
|
|
|
[this_weak_ptr = weak_ptr_factory_.GetWeakPtr(),
|
|
|
|
|
observer_refptr =
|
|
|
|
|
rtc::scoped_refptr<SetSessionDescriptionObserver>(observer),
|
|
|
|
|
desc = std::unique_ptr<SessionDescriptionInterface>(desc_ptr)](
|
|
|
|
|
std::function<void()> operations_chain_callback) mutable {
|
2021-07-30 22:30:23 +02:00
|
|
|
// Abort early if `this_weak_ptr` is no longer valid.
|
2020-09-28 13:02:07 +00:00
|
|
|
if (!this_weak_ptr) {
|
|
|
|
|
// For consistency with SetSessionDescriptionObserverAdapter whose
|
|
|
|
|
// posted messages doesn't get processed when the PC is destroyed, we
|
2021-07-30 22:30:23 +02:00
|
|
|
// do not inform `observer_refptr` that the operation failed.
|
2020-09-28 13:02:07 +00:00
|
|
|
operations_chain_callback();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// SetSessionDescriptionObserverAdapter takes care of making sure the
|
2021-07-30 22:30:23 +02:00
|
|
|
// `observer_refptr` is invoked in a posted message.
|
2020-09-28 13:02:07 +00:00
|
|
|
this_weak_ptr->DoSetRemoteDescription(
|
2022-01-12 13:11:04 +01:00
|
|
|
std::make_unique<RemoteDescriptionOperation>(
|
|
|
|
|
this_weak_ptr.get(), std::move(desc),
|
|
|
|
|
rtc::make_ref_counted<SetSessionDescriptionObserverAdapter>(
|
|
|
|
|
this_weak_ptr, observer_refptr),
|
|
|
|
|
std::move(operations_chain_callback)));
|
2020-09-28 13:02:07 +00:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::SetRemoteDescription(
|
|
|
|
|
std::unique_ptr<SessionDescriptionInterface> desc,
|
|
|
|
|
rtc::scoped_refptr<SetRemoteDescriptionObserverInterface> observer) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
// Chain this operation. If asynchronous operations are pending on the chain,
|
|
|
|
|
// this operation will be queued to be invoked, otherwise the contents of the
|
|
|
|
|
// lambda will execute immediately.
|
|
|
|
|
operations_chain_->ChainOperation(
|
|
|
|
|
[this_weak_ptr = weak_ptr_factory_.GetWeakPtr(), observer,
|
|
|
|
|
desc = std::move(desc)](
|
|
|
|
|
std::function<void()> operations_chain_callback) mutable {
|
2022-01-12 13:11:04 +01:00
|
|
|
if (!observer) {
|
|
|
|
|
RTC_DLOG(LS_ERROR) << "SetRemoteDescription - observer is NULL.";
|
|
|
|
|
operations_chain_callback();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-30 22:30:23 +02:00
|
|
|
// Abort early if `this_weak_ptr` is no longer valid.
|
2020-09-28 13:02:07 +00:00
|
|
|
if (!this_weak_ptr) {
|
|
|
|
|
observer->OnSetRemoteDescriptionComplete(RTCError(
|
|
|
|
|
RTCErrorType::INTERNAL_ERROR,
|
|
|
|
|
"SetRemoteDescription failed because the session was shut down"));
|
|
|
|
|
operations_chain_callback();
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-01-12 13:11:04 +01:00
|
|
|
|
|
|
|
|
this_weak_ptr->DoSetRemoteDescription(
|
|
|
|
|
std::make_unique<RemoteDescriptionOperation>(
|
|
|
|
|
this_weak_ptr.get(), std::move(desc), std::move(observer),
|
|
|
|
|
std::move(operations_chain_callback)));
|
2020-09-28 13:02:07 +00:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-17 19:19:56 +01:00
|
|
|
RTCError SdpOfferAnswerHandler::ReplaceRemoteDescription(
|
2021-04-26 21:04:26 +02:00
|
|
|
std::unique_ptr<SessionDescriptionInterface> desc,
|
2022-01-17 19:19:56 +01:00
|
|
|
SdpType sdp_type,
|
|
|
|
|
std::unique_ptr<SessionDescriptionInterface>* replaced_description) {
|
|
|
|
|
RTC_DCHECK(replaced_description);
|
|
|
|
|
if (sdp_type == SdpType::kAnswer) {
|
|
|
|
|
*replaced_description = pending_remote_description_
|
|
|
|
|
? std::move(pending_remote_description_)
|
|
|
|
|
: std::move(current_remote_description_);
|
2020-09-28 13:02:07 +00:00
|
|
|
current_remote_description_ = std::move(desc);
|
|
|
|
|
pending_remote_description_ = nullptr;
|
|
|
|
|
current_local_description_ = std::move(pending_local_description_);
|
|
|
|
|
} else {
|
2022-01-17 19:19:56 +01:00
|
|
|
*replaced_description = std::move(pending_remote_description_);
|
2020-09-28 13:02:07 +00:00
|
|
|
pending_remote_description_ = std::move(desc);
|
|
|
|
|
}
|
2022-01-17 19:19:56 +01:00
|
|
|
|
2020-09-28 13:02:07 +00:00
|
|
|
// The session description to apply now must be accessed by
|
2021-07-30 22:30:23 +02:00
|
|
|
// `remote_description()`.
|
2022-01-17 19:19:56 +01:00
|
|
|
const cricket::SessionDescription* session_desc =
|
|
|
|
|
remote_description()->description();
|
2020-09-28 13:02:07 +00:00
|
|
|
|
2024-01-11 22:15:27 +01:00
|
|
|
const auto* local = local_description();
|
|
|
|
|
|
2022-09-08 18:38:10 +02:00
|
|
|
// NOTE: This will perform a BlockingCall() to the network thread.
|
2024-01-11 22:15:27 +01:00
|
|
|
return transport_controller_s()->SetRemoteDescription(
|
|
|
|
|
sdp_type, local ? local->description() : nullptr, session_desc);
|
2022-01-17 19:19:56 +01:00
|
|
|
}
|
2022-01-07 16:22:14 +00:00
|
|
|
|
2022-01-17 19:19:56 +01:00
|
|
|
void SdpOfferAnswerHandler::ApplyRemoteDescription(
|
|
|
|
|
std::unique_ptr<RemoteDescriptionOperation> operation) {
|
|
|
|
|
TRACE_EVENT0("webrtc", "SdpOfferAnswerHandler::ApplyRemoteDescription");
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
RTC_DCHECK(operation->description());
|
2022-01-07 16:22:14 +00:00
|
|
|
|
2022-09-01 15:39:50 +02:00
|
|
|
// Invalidate the stats caches to make sure that they get
|
|
|
|
|
// updated next time getStats() gets called, as updating the session
|
|
|
|
|
// description affects the stats.
|
|
|
|
|
pc_->ClearStatsCache();
|
2022-01-17 19:19:56 +01:00
|
|
|
|
2023-05-02 11:28:10 +02:00
|
|
|
if (!operation->ReplaceRemoteDescriptionAndCheckError())
|
2022-01-17 19:19:56 +01:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (!operation->UpdateChannels())
|
|
|
|
|
return;
|
2020-09-28 13:02:07 +00:00
|
|
|
|
|
|
|
|
// NOTE: Candidates allocation will be initiated only when
|
|
|
|
|
// SetLocalDescription is called.
|
2022-01-17 19:19:56 +01:00
|
|
|
if (!operation->UpdateSessionState())
|
|
|
|
|
return;
|
2020-09-28 13:02:07 +00:00
|
|
|
|
2022-01-17 19:19:56 +01:00
|
|
|
if (!operation->UseCandidatesInRemoteDescription())
|
|
|
|
|
return;
|
2020-09-28 13:02:07 +00:00
|
|
|
|
2022-01-17 19:19:56 +01:00
|
|
|
if (operation->old_remote_description()) {
|
2020-09-28 13:02:07 +00:00
|
|
|
for (const cricket::ContentInfo& content :
|
2022-01-17 19:19:56 +01:00
|
|
|
operation->old_remote_description()->description()->contents()) {
|
2020-09-28 13:02:07 +00:00
|
|
|
// Check if this new SessionDescription contains new ICE ufrag and
|
|
|
|
|
// password that indicates the remote peer requests an ICE restart.
|
|
|
|
|
// TODO(deadbeef): When we start storing both the current and pending
|
|
|
|
|
// remote description, this should reset pending_ice_restarts and compare
|
|
|
|
|
// against the current description.
|
2022-01-17 19:19:56 +01:00
|
|
|
if (CheckForRemoteIceRestart(operation->old_remote_description(),
|
|
|
|
|
remote_description(), content.name)) {
|
|
|
|
|
if (operation->type() == SdpType::kOffer) {
|
2020-09-28 13:02:07 +00:00
|
|
|
pending_ice_restarts_.insert(content.name);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// We retain all received candidates only if ICE is not restarted.
|
|
|
|
|
// When ICE is restarted, all previous candidates belong to an old
|
|
|
|
|
// generation and should not be kept.
|
|
|
|
|
// TODO(deadbeef): This goes against the W3C spec which says the remote
|
|
|
|
|
// description should only contain candidates from the last set remote
|
|
|
|
|
// description plus any candidates added since then. We should remove
|
|
|
|
|
// this once we're sure it won't break anything.
|
|
|
|
|
WebRtcSessionDescriptionFactory::CopyCandidatesFromSessionDescription(
|
2022-01-17 19:19:56 +01:00
|
|
|
operation->old_remote_description(), content.name,
|
|
|
|
|
mutable_remote_description());
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-17 19:19:56 +01:00
|
|
|
if (operation->HaveSessionError())
|
|
|
|
|
return;
|
2020-09-28 13:02:07 +00:00
|
|
|
|
|
|
|
|
// Set the the ICE connection state to connecting since the connection may
|
|
|
|
|
// become writable with peer reflexive candidates before any remote candidate
|
|
|
|
|
// is signaled.
|
|
|
|
|
// TODO(pthatcher): This is a short-term solution for crbug/446908. A real fix
|
|
|
|
|
// is to have a new signal the indicates a change in checking state from the
|
|
|
|
|
// transport and expose a new checking() member from transport that can be
|
|
|
|
|
// read to determine the current checking state. The existing SignalConnecting
|
|
|
|
|
// actually means "gathering candidates", so cannot be be used here.
|
|
|
|
|
if (remote_description()->GetType() != SdpType::kOffer &&
|
|
|
|
|
remote_description()->number_of_mediasections() > 0u &&
|
2022-01-28 13:08:34 +00:00
|
|
|
pc_->ice_connection_state_internal() ==
|
2020-09-28 13:02:07 +00:00
|
|
|
PeerConnectionInterface::kIceConnectionNew) {
|
|
|
|
|
pc_->SetIceConnectionState(PeerConnectionInterface::kIceConnectionChecking);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If setting the description decided our SSL role, allocate any necessary
|
|
|
|
|
// SCTP sids.
|
2023-03-22 08:25:38 +01:00
|
|
|
AllocateSctpSids();
|
2020-09-28 13:02:07 +00:00
|
|
|
|
2022-01-17 19:19:56 +01:00
|
|
|
if (operation->unified_plan()) {
|
|
|
|
|
ApplyRemoteDescriptionUpdateTransceiverState(operation->type());
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
2023-12-12 13:15:03 +01:00
|
|
|
remote_peer_supports_msid_ =
|
|
|
|
|
remote_description()->description()->msid_signaling() !=
|
|
|
|
|
cricket::kMsidSignalingNotUsed;
|
2020-09-28 13:02:07 +00:00
|
|
|
|
2022-01-17 19:19:56 +01:00
|
|
|
if (!operation->unified_plan()) {
|
2022-01-07 16:22:14 +00:00
|
|
|
PlanBUpdateSendersAndReceivers(
|
2023-12-12 13:15:03 +01:00
|
|
|
GetFirstAudioContent(remote_description()->description()),
|
|
|
|
|
GetFirstAudioContentDescription(remote_description()->description()),
|
|
|
|
|
GetFirstVideoContent(remote_description()->description()),
|
|
|
|
|
GetFirstVideoContentDescription(remote_description()->description()));
|
2022-01-07 16:22:14 +00:00
|
|
|
}
|
|
|
|
|
|
2022-01-17 19:19:56 +01:00
|
|
|
if (operation->type() == SdpType::kAnswer) {
|
2022-01-12 13:11:04 +01:00
|
|
|
if (local_ice_credentials_to_replace_->SatisfiesIceRestart(
|
|
|
|
|
*current_local_description_)) {
|
|
|
|
|
local_ice_credentials_to_replace_->ClearIceCredentials();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RemoveStoppedTransceivers();
|
2022-01-07 16:22:14 +00:00
|
|
|
}
|
|
|
|
|
|
2022-01-17 19:19:56 +01:00
|
|
|
// Consider the operation complete at this point.
|
|
|
|
|
operation->SignalCompletion();
|
|
|
|
|
|
|
|
|
|
SetRemoteDescriptionPostProcess(operation->type() == SdpType::kAnswer);
|
2022-01-07 16:22:14 +00:00
|
|
|
}
|
|
|
|
|
|
2022-01-07 18:33:12 +00:00
|
|
|
void SdpOfferAnswerHandler::ApplyRemoteDescriptionUpdateTransceiverState(
|
|
|
|
|
SdpType sdp_type) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
RTC_DCHECK(IsUnifiedPlan());
|
2022-05-23 14:57:47 +00:00
|
|
|
if (!ConfiguredForMedia()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-01-07 18:33:12 +00:00
|
|
|
std::vector<rtc::scoped_refptr<RtpTransceiverInterface>>
|
|
|
|
|
now_receiving_transceivers;
|
|
|
|
|
std::vector<rtc::scoped_refptr<RtpTransceiverInterface>> remove_list;
|
|
|
|
|
std::vector<rtc::scoped_refptr<MediaStreamInterface>> added_streams;
|
|
|
|
|
std::vector<rtc::scoped_refptr<MediaStreamInterface>> removed_streams;
|
|
|
|
|
for (const auto& transceiver_ext : transceivers()->List()) {
|
|
|
|
|
const auto transceiver = transceiver_ext->internal();
|
|
|
|
|
const ContentInfo* content =
|
|
|
|
|
FindMediaSectionForTransceiver(transceiver, remote_description());
|
|
|
|
|
if (!content) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const MediaContentDescription* media_desc = content->media_description();
|
|
|
|
|
RtpTransceiverDirection local_direction =
|
|
|
|
|
RtpTransceiverDirectionReversed(media_desc->direction());
|
2022-05-02 15:47:52 +02:00
|
|
|
// Remember the previous remote streams if this is a remote offer. This
|
|
|
|
|
// makes it possible to rollback modifications to the streams.
|
|
|
|
|
if (sdp_type == SdpType::kOffer) {
|
|
|
|
|
transceivers()
|
|
|
|
|
->StableState(transceiver_ext)
|
|
|
|
|
->SetRemoteStreamIds(transceiver->receiver()->stream_ids());
|
|
|
|
|
}
|
2022-01-07 18:33:12 +00:00
|
|
|
// Roughly the same as steps 2.2.8.6 of section 4.4.1.6 "Set the
|
|
|
|
|
// RTCSessionDescription: Set the associated remote streams given
|
|
|
|
|
// transceiver.[[Receiver]], msids, addList, and removeList".
|
|
|
|
|
// https://w3c.github.io/webrtc-pc/#set-the-rtcsessiondescription
|
|
|
|
|
if (RtpTransceiverDirectionHasRecv(local_direction)) {
|
|
|
|
|
std::vector<std::string> stream_ids;
|
|
|
|
|
if (!media_desc->streams().empty()) {
|
|
|
|
|
// The remote description has signaled the stream IDs.
|
|
|
|
|
stream_ids = media_desc->streams()[0].stream_ids();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RTC_LOG(LS_INFO) << "Processing the MSIDs for MID=" << content->name
|
|
|
|
|
<< " (" << GetStreamIdsString(stream_ids) << ").";
|
|
|
|
|
SetAssociatedRemoteStreams(transceiver->receiver_internal(), stream_ids,
|
|
|
|
|
&added_streams, &removed_streams);
|
|
|
|
|
// From the WebRTC specification, steps 2.2.8.5/6 of section 4.4.1.6
|
|
|
|
|
// "Set the RTCSessionDescription: If direction is sendrecv or recvonly,
|
|
|
|
|
// and transceiver's current direction is neither sendrecv nor recvonly,
|
|
|
|
|
// process the addition of a remote track for the media description.
|
|
|
|
|
if (!transceiver->fired_direction() ||
|
|
|
|
|
!RtpTransceiverDirectionHasRecv(*transceiver->fired_direction())) {
|
|
|
|
|
RTC_LOG(LS_INFO) << "Processing the addition of a remote track for MID="
|
|
|
|
|
<< content->name << ".";
|
|
|
|
|
// Since the transceiver is passed to the user in an
|
|
|
|
|
// OnTrack event, we must use the proxied transceiver.
|
|
|
|
|
now_receiving_transceivers.push_back(transceiver_ext);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 2.2.8.1.9: If direction is "sendonly" or "inactive", and transceiver's
|
|
|
|
|
// [[FiredDirection]] slot is either "sendrecv" or "recvonly", process the
|
|
|
|
|
// removal of a remote track for the media description, given transceiver,
|
|
|
|
|
// removeList, and muteTracks.
|
|
|
|
|
if (!RtpTransceiverDirectionHasRecv(local_direction) &&
|
|
|
|
|
(transceiver->fired_direction() &&
|
|
|
|
|
RtpTransceiverDirectionHasRecv(*transceiver->fired_direction()))) {
|
|
|
|
|
ProcessRemovalOfRemoteTrack(transceiver_ext, &remove_list,
|
|
|
|
|
&removed_streams);
|
|
|
|
|
}
|
|
|
|
|
// 2.2.8.1.10: Set transceiver's [[FiredDirection]] slot to direction.
|
2022-05-02 15:47:52 +02:00
|
|
|
if (sdp_type == SdpType::kOffer) {
|
|
|
|
|
// Remember the previous fired direction if this is a remote offer. This
|
|
|
|
|
// makes it possible to rollback modifications to [[FiredDirection]],
|
|
|
|
|
// which is necessary for "ontrack" to fire in or after rollback.
|
|
|
|
|
transceivers()
|
|
|
|
|
->StableState(transceiver_ext)
|
|
|
|
|
->SetFiredDirection(transceiver->fired_direction());
|
|
|
|
|
}
|
2022-01-07 18:33:12 +00:00
|
|
|
transceiver->set_fired_direction(local_direction);
|
|
|
|
|
// 2.2.8.1.11: If description is of type "answer" or "pranswer", then run
|
|
|
|
|
// the following steps:
|
|
|
|
|
if (sdp_type == SdpType::kPrAnswer || sdp_type == SdpType::kAnswer) {
|
|
|
|
|
// 2.2.8.1.11.1: Set transceiver's [[CurrentDirection]] slot to
|
|
|
|
|
// direction.
|
|
|
|
|
transceiver->set_current_direction(local_direction);
|
|
|
|
|
// 2.2.8.1.11.[3-6]: Set the transport internal slots.
|
|
|
|
|
if (transceiver->mid()) {
|
|
|
|
|
auto dtls_transport = LookupDtlsTransportByMid(
|
2022-02-09 12:08:47 +00:00
|
|
|
context_->network_thread(), transport_controller_s(),
|
2022-01-28 17:41:30 +00:00
|
|
|
*transceiver->mid());
|
2022-01-07 18:33:12 +00:00
|
|
|
transceiver->sender_internal()->set_transport(dtls_transport);
|
|
|
|
|
transceiver->receiver_internal()->set_transport(dtls_transport);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 2.2.8.1.12: If the media description is rejected, and transceiver is
|
|
|
|
|
// not already stopped, stop the RTCRtpTransceiver transceiver.
|
|
|
|
|
if (content->rejected && !transceiver->stopped()) {
|
|
|
|
|
RTC_LOG(LS_INFO) << "Stopping transceiver for MID=" << content->name
|
|
|
|
|
<< " since the media section was rejected.";
|
|
|
|
|
transceiver->StopTransceiverProcedure();
|
|
|
|
|
}
|
|
|
|
|
if (!content->rejected && RtpTransceiverDirectionHasRecv(local_direction)) {
|
|
|
|
|
if (!media_desc->streams().empty() &&
|
|
|
|
|
media_desc->streams()[0].has_ssrcs()) {
|
|
|
|
|
uint32_t ssrc = media_desc->streams()[0].first_ssrc();
|
|
|
|
|
transceiver->receiver_internal()->SetupMediaChannel(ssrc);
|
|
|
|
|
} else {
|
|
|
|
|
transceiver->receiver_internal()->SetupUnsignaledMediaChannel();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Once all processing has finished, fire off callbacks.
|
|
|
|
|
auto observer = pc_->Observer();
|
|
|
|
|
for (const auto& transceiver : now_receiving_transceivers) {
|
2022-07-04 14:36:37 +02:00
|
|
|
pc_->legacy_stats()->AddTrack(transceiver->receiver()->track().get());
|
2022-01-07 18:33:12 +00:00
|
|
|
observer->OnTrack(transceiver);
|
|
|
|
|
observer->OnAddTrack(transceiver->receiver(),
|
|
|
|
|
transceiver->receiver()->streams());
|
|
|
|
|
}
|
|
|
|
|
for (const auto& stream : added_streams) {
|
|
|
|
|
observer->OnAddStream(stream);
|
|
|
|
|
}
|
|
|
|
|
for (const auto& transceiver : remove_list) {
|
|
|
|
|
observer->OnRemoveTrack(transceiver->receiver());
|
|
|
|
|
}
|
|
|
|
|
for (const auto& stream : removed_streams) {
|
|
|
|
|
observer->OnRemoveStream(stream);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-07 16:22:14 +00:00
|
|
|
void SdpOfferAnswerHandler::PlanBUpdateSendersAndReceivers(
|
|
|
|
|
const cricket::ContentInfo* audio_content,
|
|
|
|
|
const cricket::AudioContentDescription* audio_desc,
|
|
|
|
|
const cricket::ContentInfo* video_content,
|
|
|
|
|
const cricket::VideoContentDescription* video_desc) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
RTC_DCHECK(!IsUnifiedPlan());
|
|
|
|
|
|
2020-09-28 13:02:07 +00:00
|
|
|
// We wait to signal new streams until we finish processing the description,
|
|
|
|
|
// since only at that point will new streams have all their tracks.
|
|
|
|
|
rtc::scoped_refptr<StreamCollection> new_streams(StreamCollection::Create());
|
|
|
|
|
|
2022-01-07 16:22:14 +00:00
|
|
|
// TODO(steveanton): When removing RTP senders/receivers in response to a
|
|
|
|
|
// rejected media section, there is some cleanup logic that expects the
|
|
|
|
|
// voice/ video channel to still be set. But in this method the voice/video
|
|
|
|
|
// channel would have been destroyed by the SetRemoteDescription caller
|
|
|
|
|
// above so the cleanup that relies on them fails to run. The RemoveSenders
|
|
|
|
|
// calls should be moved to right before the DestroyChannel calls to fix
|
|
|
|
|
// this.
|
|
|
|
|
|
|
|
|
|
// Find all audio rtp streams and create corresponding remote AudioTracks
|
|
|
|
|
// and MediaStreams.
|
|
|
|
|
if (audio_content) {
|
|
|
|
|
if (audio_content->rejected) {
|
|
|
|
|
RemoveSenders(cricket::MEDIA_TYPE_AUDIO);
|
|
|
|
|
} else {
|
|
|
|
|
bool default_audio_track_needed =
|
|
|
|
|
!remote_peer_supports_msid_ &&
|
|
|
|
|
RtpTransceiverDirectionHasSend(audio_desc->direction());
|
|
|
|
|
UpdateRemoteSendersList(GetActiveStreams(audio_desc),
|
|
|
|
|
default_audio_track_needed, audio_desc->type(),
|
2022-04-20 14:26:50 +02:00
|
|
|
new_streams.get());
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
2022-01-07 16:22:14 +00:00
|
|
|
}
|
2020-09-28 13:02:07 +00:00
|
|
|
|
2022-01-07 16:22:14 +00:00
|
|
|
// Find all video rtp streams and create corresponding remote VideoTracks
|
|
|
|
|
// and MediaStreams.
|
|
|
|
|
if (video_content) {
|
|
|
|
|
if (video_content->rejected) {
|
|
|
|
|
RemoveSenders(cricket::MEDIA_TYPE_VIDEO);
|
|
|
|
|
} else {
|
|
|
|
|
bool default_video_track_needed =
|
|
|
|
|
!remote_peer_supports_msid_ &&
|
|
|
|
|
RtpTransceiverDirectionHasSend(video_desc->direction());
|
|
|
|
|
UpdateRemoteSendersList(GetActiveStreams(video_desc),
|
|
|
|
|
default_video_track_needed, video_desc->type(),
|
2022-04-20 14:26:50 +02:00
|
|
|
new_streams.get());
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-07 16:22:14 +00:00
|
|
|
// Iterate new_streams and notify the observer about new MediaStreams.
|
|
|
|
|
auto observer = pc_->Observer();
|
|
|
|
|
for (size_t i = 0; i < new_streams->count(); ++i) {
|
|
|
|
|
MediaStreamInterface* new_stream = new_streams->at(i);
|
2022-07-04 14:36:37 +02:00
|
|
|
pc_->legacy_stats()->AddStream(new_stream);
|
2022-01-07 16:22:14 +00:00
|
|
|
observer->OnAddStream(rtc::scoped_refptr<MediaStreamInterface>(new_stream));
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
|
2022-01-07 16:22:14 +00:00
|
|
|
UpdateEndedRemoteMediaStreams();
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::DoSetLocalDescription(
|
|
|
|
|
std::unique_ptr<SessionDescriptionInterface> desc,
|
|
|
|
|
rtc::scoped_refptr<SetLocalDescriptionObserverInterface> observer) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
TRACE_EVENT0("webrtc", "SdpOfferAnswerHandler::DoSetLocalDescription");
|
|
|
|
|
|
|
|
|
|
if (!observer) {
|
|
|
|
|
RTC_LOG(LS_ERROR) << "SetLocalDescription - observer is NULL.";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!desc) {
|
|
|
|
|
observer->OnSetLocalDescriptionComplete(
|
|
|
|
|
RTCError(RTCErrorType::INTERNAL_ERROR, "SessionDescription is NULL."));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If a session error has occurred the PeerConnection is in a possibly
|
|
|
|
|
// inconsistent state so fail right away.
|
2020-10-01 16:47:23 +00:00
|
|
|
if (session_error() != SessionError::kNone) {
|
|
|
|
|
std::string error_message = GetSessionErrorMsg();
|
2020-09-28 13:02:07 +00:00
|
|
|
RTC_LOG(LS_ERROR) << "SetLocalDescription: " << error_message;
|
|
|
|
|
observer->OnSetLocalDescriptionComplete(
|
|
|
|
|
RTCError(RTCErrorType::INTERNAL_ERROR, std::move(error_message)));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// For SLD we support only explicit rollback.
|
|
|
|
|
if (desc->GetType() == SdpType::kRollback) {
|
|
|
|
|
if (IsUnifiedPlan()) {
|
|
|
|
|
observer->OnSetLocalDescriptionComplete(Rollback(desc->GetType()));
|
|
|
|
|
} else {
|
|
|
|
|
observer->OnSetLocalDescriptionComplete(
|
|
|
|
|
RTCError(RTCErrorType::UNSUPPORTED_OPERATION,
|
|
|
|
|
"Rollback not supported in Plan B"));
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-26 21:04:26 +02:00
|
|
|
std::map<std::string, const cricket::ContentGroup*> bundle_groups_by_mid =
|
|
|
|
|
GetBundleGroupsByMid(desc->description());
|
|
|
|
|
RTCError error = ValidateSessionDescription(desc.get(), cricket::CS_LOCAL,
|
|
|
|
|
bundle_groups_by_mid);
|
2020-09-28 13:02:07 +00:00
|
|
|
if (!error.ok()) {
|
|
|
|
|
std::string error_message = GetSetDescriptionErrorMessage(
|
|
|
|
|
cricket::CS_LOCAL, desc->GetType(), error);
|
|
|
|
|
RTC_LOG(LS_ERROR) << error_message;
|
|
|
|
|
observer->OnSetLocalDescriptionComplete(
|
2023-10-06 11:32:41 +00:00
|
|
|
RTCError(error.type(), std::move(error_message)));
|
2020-09-28 13:02:07 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Grab the description type before moving ownership to ApplyLocalDescription,
|
|
|
|
|
// which may destroy it before returning.
|
|
|
|
|
const SdpType type = desc->GetType();
|
|
|
|
|
|
2021-04-26 21:04:26 +02:00
|
|
|
error = ApplyLocalDescription(std::move(desc), bundle_groups_by_mid);
|
2021-07-30 22:30:23 +02:00
|
|
|
// `desc` may be destroyed at this point.
|
2020-09-28 13:02:07 +00:00
|
|
|
|
|
|
|
|
if (!error.ok()) {
|
|
|
|
|
// If ApplyLocalDescription fails, the PeerConnection could be in an
|
|
|
|
|
// inconsistent state, so act conservatively here and set the session error
|
|
|
|
|
// so that future calls to SetLocalDescription/SetRemoteDescription fail.
|
2020-10-01 16:47:23 +00:00
|
|
|
SetSessionError(SessionError::kContent, error.message());
|
2020-09-28 13:02:07 +00:00
|
|
|
std::string error_message =
|
|
|
|
|
GetSetDescriptionErrorMessage(cricket::CS_LOCAL, type, error);
|
|
|
|
|
RTC_LOG(LS_ERROR) << error_message;
|
|
|
|
|
observer->OnSetLocalDescriptionComplete(
|
|
|
|
|
RTCError(RTCErrorType::INTERNAL_ERROR, std::move(error_message)));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
RTC_DCHECK(local_description());
|
|
|
|
|
|
|
|
|
|
if (local_description()->GetType() == SdpType::kAnswer) {
|
2020-10-01 16:47:23 +00:00
|
|
|
RemoveStoppedTransceivers();
|
2020-09-28 13:02:07 +00:00
|
|
|
|
|
|
|
|
// TODO(deadbeef): We already had to hop to the network thread for
|
|
|
|
|
// MaybeStartGathering...
|
2022-09-08 18:38:10 +02:00
|
|
|
context_->network_thread()->BlockingCall(
|
|
|
|
|
[this] { port_allocator()->DiscardCandidatePool(); });
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
observer->OnSetLocalDescriptionComplete(RTCError::OK());
|
2020-10-09 05:43:53 +00:00
|
|
|
pc_->NoteUsageEvent(UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED);
|
2020-09-28 13:02:07 +00:00
|
|
|
|
|
|
|
|
// Check if negotiation is needed. We must do this after informing the
|
|
|
|
|
// observer that SetLocalDescription() has completed to ensure negotiation is
|
|
|
|
|
// not needed prior to the promise resolving.
|
|
|
|
|
if (IsUnifiedPlan()) {
|
|
|
|
|
bool was_negotiation_needed = is_negotiation_needed_;
|
|
|
|
|
UpdateNegotiationNeeded();
|
|
|
|
|
if (signaling_state() == PeerConnectionInterface::kStable &&
|
|
|
|
|
was_negotiation_needed && is_negotiation_needed_) {
|
|
|
|
|
// Legacy version.
|
|
|
|
|
pc_->Observer()->OnRenegotiationNeeded();
|
|
|
|
|
// Spec-compliant version; the event may get invalidated before firing.
|
|
|
|
|
GenerateNegotiationNeededEvent();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MaybeStartGathering needs to be called after informing the observer so that
|
|
|
|
|
// we don't signal any candidates before signaling that SetLocalDescription
|
|
|
|
|
// completed.
|
2022-02-09 12:08:47 +00:00
|
|
|
transport_controller_s()->MaybeStartGathering();
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::DoCreateOffer(
|
|
|
|
|
const PeerConnectionInterface::RTCOfferAnswerOptions& options,
|
|
|
|
|
rtc::scoped_refptr<CreateSessionDescriptionObserver> observer) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
TRACE_EVENT0("webrtc", "SdpOfferAnswerHandler::DoCreateOffer");
|
|
|
|
|
|
|
|
|
|
if (!observer) {
|
|
|
|
|
RTC_LOG(LS_ERROR) << "CreateOffer - observer is NULL.";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pc_->IsClosed()) {
|
|
|
|
|
std::string error = "CreateOffer called when PeerConnection is closed.";
|
|
|
|
|
RTC_LOG(LS_ERROR) << error;
|
2020-10-05 07:01:09 +00:00
|
|
|
pc_->message_handler()->PostCreateSessionDescriptionFailure(
|
2022-04-20 14:26:50 +02:00
|
|
|
observer.get(),
|
|
|
|
|
RTCError(RTCErrorType::INVALID_STATE, std::move(error)));
|
2020-09-28 13:02:07 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If a session error has occurred the PeerConnection is in a possibly
|
|
|
|
|
// inconsistent state so fail right away.
|
2020-10-01 16:47:23 +00:00
|
|
|
if (session_error() != SessionError::kNone) {
|
|
|
|
|
std::string error_message = GetSessionErrorMsg();
|
2020-09-28 13:02:07 +00:00
|
|
|
RTC_LOG(LS_ERROR) << "CreateOffer: " << error_message;
|
2020-10-05 07:01:09 +00:00
|
|
|
pc_->message_handler()->PostCreateSessionDescriptionFailure(
|
2022-04-20 14:26:50 +02:00
|
|
|
observer.get(),
|
2020-09-28 13:02:07 +00:00
|
|
|
RTCError(RTCErrorType::INTERNAL_ERROR, std::move(error_message)));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!ValidateOfferAnswerOptions(options)) {
|
|
|
|
|
std::string error = "CreateOffer called with invalid options.";
|
|
|
|
|
RTC_LOG(LS_ERROR) << error;
|
2020-10-05 07:01:09 +00:00
|
|
|
pc_->message_handler()->PostCreateSessionDescriptionFailure(
|
2022-04-20 14:26:50 +02:00
|
|
|
observer.get(),
|
|
|
|
|
RTCError(RTCErrorType::INVALID_PARAMETER, std::move(error)));
|
2020-09-28 13:02:07 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Legacy handling for offer_to_receive_audio and offer_to_receive_video.
|
|
|
|
|
// Specified in WebRTC section 4.4.3.2 "Legacy configuration extensions".
|
|
|
|
|
if (IsUnifiedPlan()) {
|
2020-10-01 16:47:23 +00:00
|
|
|
RTCError error = HandleLegacyOfferOptions(options);
|
2020-09-28 13:02:07 +00:00
|
|
|
if (!error.ok()) {
|
2020-10-05 07:01:09 +00:00
|
|
|
pc_->message_handler()->PostCreateSessionDescriptionFailure(
|
2022-04-20 14:26:50 +02:00
|
|
|
observer.get(), std::move(error));
|
2020-09-28 13:02:07 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cricket::MediaSessionOptions session_options;
|
2020-10-01 10:23:33 +00:00
|
|
|
GetOptionsForOffer(options, &session_options);
|
2022-04-20 14:26:50 +02:00
|
|
|
webrtc_session_desc_factory_->CreateOffer(observer.get(), options,
|
|
|
|
|
session_options);
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::CreateAnswer(
|
|
|
|
|
CreateSessionDescriptionObserver* observer,
|
|
|
|
|
const PeerConnectionInterface::RTCOfferAnswerOptions& options) {
|
2021-06-07 13:30:46 +02:00
|
|
|
TRACE_EVENT0("webrtc", "SdpOfferAnswerHandler::CreateAnswer");
|
2020-09-28 13:02:07 +00:00
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
// Chain this operation. If asynchronous operations are pending on the chain,
|
|
|
|
|
// this operation will be queued to be invoked, otherwise the contents of the
|
|
|
|
|
// lambda will execute immediately.
|
|
|
|
|
operations_chain_->ChainOperation(
|
|
|
|
|
[this_weak_ptr = weak_ptr_factory_.GetWeakPtr(),
|
|
|
|
|
observer_refptr =
|
|
|
|
|
rtc::scoped_refptr<CreateSessionDescriptionObserver>(observer),
|
|
|
|
|
options](std::function<void()> operations_chain_callback) {
|
2021-07-30 22:30:23 +02:00
|
|
|
// Abort early if `this_weak_ptr` is no longer valid.
|
2020-09-28 13:02:07 +00:00
|
|
|
if (!this_weak_ptr) {
|
|
|
|
|
observer_refptr->OnFailure(RTCError(
|
|
|
|
|
RTCErrorType::INTERNAL_ERROR,
|
|
|
|
|
"CreateAnswer failed because the session was shut down"));
|
|
|
|
|
operations_chain_callback();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// The operation completes asynchronously when the wrapper is invoked.
|
2021-08-23 15:48:06 +02:00
|
|
|
auto observer_wrapper = rtc::make_ref_counted<
|
|
|
|
|
CreateSessionDescriptionObserverOperationWrapper>(
|
|
|
|
|
std::move(observer_refptr), std::move(operations_chain_callback));
|
2020-09-28 13:02:07 +00:00
|
|
|
this_weak_ptr->DoCreateAnswer(options, observer_wrapper);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::DoCreateAnswer(
|
|
|
|
|
const PeerConnectionInterface::RTCOfferAnswerOptions& options,
|
|
|
|
|
rtc::scoped_refptr<CreateSessionDescriptionObserver> observer) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
TRACE_EVENT0("webrtc", "SdpOfferAnswerHandler::DoCreateAnswer");
|
|
|
|
|
if (!observer) {
|
|
|
|
|
RTC_LOG(LS_ERROR) << "CreateAnswer - observer is NULL.";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If a session error has occurred the PeerConnection is in a possibly
|
|
|
|
|
// inconsistent state so fail right away.
|
2020-10-01 16:47:23 +00:00
|
|
|
if (session_error() != SessionError::kNone) {
|
|
|
|
|
std::string error_message = GetSessionErrorMsg();
|
2020-09-28 13:02:07 +00:00
|
|
|
RTC_LOG(LS_ERROR) << "CreateAnswer: " << error_message;
|
2020-10-05 07:01:09 +00:00
|
|
|
pc_->message_handler()->PostCreateSessionDescriptionFailure(
|
2022-04-20 14:26:50 +02:00
|
|
|
observer.get(),
|
2020-09-28 13:02:07 +00:00
|
|
|
RTCError(RTCErrorType::INTERNAL_ERROR, std::move(error_message)));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!(signaling_state_ == PeerConnectionInterface::kHaveRemoteOffer ||
|
|
|
|
|
signaling_state_ == PeerConnectionInterface::kHaveLocalPrAnswer)) {
|
|
|
|
|
std::string error =
|
|
|
|
|
"PeerConnection cannot create an answer in a state other than "
|
|
|
|
|
"have-remote-offer or have-local-pranswer.";
|
|
|
|
|
RTC_LOG(LS_ERROR) << error;
|
2020-10-05 07:01:09 +00:00
|
|
|
pc_->message_handler()->PostCreateSessionDescriptionFailure(
|
2022-04-20 14:26:50 +02:00
|
|
|
observer.get(),
|
|
|
|
|
RTCError(RTCErrorType::INVALID_STATE, std::move(error)));
|
2020-09-28 13:02:07 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The remote description should be set if we're in the right state.
|
|
|
|
|
RTC_DCHECK(remote_description());
|
|
|
|
|
|
|
|
|
|
if (IsUnifiedPlan()) {
|
|
|
|
|
if (options.offer_to_receive_audio !=
|
|
|
|
|
PeerConnectionInterface::RTCOfferAnswerOptions::kUndefined) {
|
|
|
|
|
RTC_LOG(LS_WARNING) << "CreateAnswer: offer_to_receive_audio is not "
|
|
|
|
|
"supported with Unified Plan semantics. Use the "
|
|
|
|
|
"RtpTransceiver API instead.";
|
|
|
|
|
}
|
|
|
|
|
if (options.offer_to_receive_video !=
|
|
|
|
|
PeerConnectionInterface::RTCOfferAnswerOptions::kUndefined) {
|
|
|
|
|
RTC_LOG(LS_WARNING) << "CreateAnswer: offer_to_receive_video is not "
|
|
|
|
|
"supported with Unified Plan semantics. Use the "
|
|
|
|
|
"RtpTransceiver API instead.";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cricket::MediaSessionOptions session_options;
|
2020-10-01 10:23:33 +00:00
|
|
|
GetOptionsForAnswer(options, &session_options);
|
2022-04-20 14:26:50 +02:00
|
|
|
webrtc_session_desc_factory_->CreateAnswer(observer.get(), session_options);
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::DoSetRemoteDescription(
|
2022-01-12 13:11:04 +01:00
|
|
|
std::unique_ptr<RemoteDescriptionOperation> operation) {
|
2020-09-28 13:02:07 +00:00
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
TRACE_EVENT0("webrtc", "SdpOfferAnswerHandler::DoSetRemoteDescription");
|
|
|
|
|
|
2022-01-12 13:11:04 +01:00
|
|
|
if (!operation->ok())
|
2020-09-28 13:02:07 +00:00
|
|
|
return;
|
|
|
|
|
|
2022-01-12 13:11:04 +01:00
|
|
|
if (operation->HaveSessionError())
|
2020-09-28 13:02:07 +00:00
|
|
|
return;
|
|
|
|
|
|
2022-01-12 13:11:04 +01:00
|
|
|
if (operation->MaybeRollback())
|
2020-09-28 13:02:07 +00:00
|
|
|
return;
|
|
|
|
|
|
2022-01-12 13:11:04 +01:00
|
|
|
operation->ReportOfferAnswerUma();
|
2020-09-28 13:02:07 +00:00
|
|
|
|
2022-01-12 13:11:04 +01:00
|
|
|
// Handle remote descriptions missing a=mid lines for interop with legacy
|
|
|
|
|
// end points.
|
|
|
|
|
FillInMissingRemoteMids(operation->description());
|
|
|
|
|
if (!operation->IsDescriptionValid())
|
2020-09-28 13:02:07 +00:00
|
|
|
return;
|
|
|
|
|
|
2022-01-17 19:19:56 +01:00
|
|
|
ApplyRemoteDescription(std::move(operation));
|
2022-01-12 13:11:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Called after a DoSetRemoteDescription operation completes.
|
|
|
|
|
void SdpOfferAnswerHandler::SetRemoteDescriptionPostProcess(bool was_answer) {
|
2020-09-28 13:02:07 +00:00
|
|
|
RTC_DCHECK(remote_description());
|
|
|
|
|
|
2022-01-12 13:11:04 +01:00
|
|
|
if (was_answer) {
|
2020-09-28 13:02:07 +00:00
|
|
|
// TODO(deadbeef): We already had to hop to the network thread for
|
|
|
|
|
// MaybeStartGathering...
|
2022-09-08 18:38:10 +02:00
|
|
|
context_->network_thread()->BlockingCall(
|
|
|
|
|
[this] { port_allocator()->DiscardCandidatePool(); });
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
|
2020-10-09 05:43:53 +00:00
|
|
|
pc_->NoteUsageEvent(UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED);
|
2020-09-28 13:02:07 +00:00
|
|
|
|
|
|
|
|
// Check if negotiation is needed. We must do this after informing the
|
2022-01-12 13:11:04 +01:00
|
|
|
// observer that SetRemoteDescription() has completed to ensure negotiation
|
|
|
|
|
// is not needed prior to the promise resolving.
|
2020-09-28 13:02:07 +00:00
|
|
|
if (IsUnifiedPlan()) {
|
|
|
|
|
bool was_negotiation_needed = is_negotiation_needed_;
|
|
|
|
|
UpdateNegotiationNeeded();
|
|
|
|
|
if (signaling_state() == PeerConnectionInterface::kStable &&
|
|
|
|
|
was_negotiation_needed && is_negotiation_needed_) {
|
|
|
|
|
// Legacy version.
|
|
|
|
|
pc_->Observer()->OnRenegotiationNeeded();
|
|
|
|
|
// Spec-compliant version; the event may get invalidated before firing.
|
|
|
|
|
GenerateNegotiationNeededEvent();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::SetAssociatedRemoteStreams(
|
|
|
|
|
rtc::scoped_refptr<RtpReceiverInternal> receiver,
|
|
|
|
|
const std::vector<std::string>& stream_ids,
|
|
|
|
|
std::vector<rtc::scoped_refptr<MediaStreamInterface>>* added_streams,
|
|
|
|
|
std::vector<rtc::scoped_refptr<MediaStreamInterface>>* removed_streams) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
std::vector<rtc::scoped_refptr<MediaStreamInterface>> media_streams;
|
|
|
|
|
for (const std::string& stream_id : stream_ids) {
|
2022-01-04 15:20:03 +01:00
|
|
|
rtc::scoped_refptr<MediaStreamInterface> stream(
|
|
|
|
|
remote_streams_->find(stream_id));
|
2020-09-28 13:02:07 +00:00
|
|
|
if (!stream) {
|
|
|
|
|
stream = MediaStreamProxy::Create(rtc::Thread::Current(),
|
|
|
|
|
MediaStream::Create(stream_id));
|
2020-10-09 11:42:17 +00:00
|
|
|
remote_streams_->AddStream(stream);
|
2020-09-28 13:02:07 +00:00
|
|
|
added_streams->push_back(stream);
|
|
|
|
|
}
|
|
|
|
|
media_streams.push_back(stream);
|
|
|
|
|
}
|
|
|
|
|
// Special case: "a=msid" missing, use random stream ID.
|
|
|
|
|
if (media_streams.empty() &&
|
|
|
|
|
!(remote_description()->description()->msid_signaling() &
|
|
|
|
|
cricket::kMsidSignalingMediaSection)) {
|
|
|
|
|
if (!missing_msid_default_stream_) {
|
|
|
|
|
missing_msid_default_stream_ = MediaStreamProxy::Create(
|
|
|
|
|
rtc::Thread::Current(), MediaStream::Create(rtc::CreateRandomUuid()));
|
|
|
|
|
added_streams->push_back(missing_msid_default_stream_);
|
|
|
|
|
}
|
|
|
|
|
media_streams.push_back(missing_msid_default_stream_);
|
|
|
|
|
}
|
|
|
|
|
std::vector<rtc::scoped_refptr<MediaStreamInterface>> previous_streams =
|
|
|
|
|
receiver->streams();
|
2022-01-12 13:11:04 +01:00
|
|
|
// SetStreams() will add/remove the receiver's track to/from the streams.
|
|
|
|
|
// This differs from the spec - the spec uses an "addList" and "removeList"
|
|
|
|
|
// to update the stream-track relationships in a later step. We do this
|
|
|
|
|
// earlier, changing the order of things, but the end-result is the same.
|
2020-09-28 13:02:07 +00:00
|
|
|
// TODO(hbos): When we remove remote_streams(), use set_stream_ids()
|
|
|
|
|
// instead. https://crbug.com/webrtc/9480
|
|
|
|
|
receiver->SetStreams(media_streams);
|
2020-10-01 16:47:23 +00:00
|
|
|
RemoveRemoteStreamsIfEmpty(previous_streams, removed_streams);
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool SdpOfferAnswerHandler::AddIceCandidate(
|
|
|
|
|
const IceCandidateInterface* ice_candidate) {
|
2021-04-01 16:26:57 +02:00
|
|
|
const AddIceCandidateResult result = AddIceCandidateInternal(ice_candidate);
|
|
|
|
|
NoteAddIceCandidateResult(result);
|
2022-01-12 13:11:04 +01:00
|
|
|
// If the return value is kAddIceCandidateFailNotReady, the candidate has
|
|
|
|
|
// been added, although not 'ready', but that's a success.
|
2021-04-01 16:26:57 +02:00
|
|
|
return result == kAddIceCandidateSuccess ||
|
|
|
|
|
result == kAddIceCandidateFailNotReady;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AddIceCandidateResult SdpOfferAnswerHandler::AddIceCandidateInternal(
|
|
|
|
|
const IceCandidateInterface* ice_candidate) {
|
2020-09-28 13:02:07 +00:00
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
TRACE_EVENT0("webrtc", "SdpOfferAnswerHandler::AddIceCandidate");
|
|
|
|
|
if (pc_->IsClosed()) {
|
|
|
|
|
RTC_LOG(LS_ERROR) << "AddIceCandidate: PeerConnection is closed.";
|
2021-04-01 16:26:57 +02:00
|
|
|
return kAddIceCandidateFailClosed;
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!remote_description()) {
|
|
|
|
|
RTC_LOG(LS_ERROR) << "AddIceCandidate: ICE candidates can't be added "
|
|
|
|
|
"without any remote session description.";
|
2021-04-01 16:26:57 +02:00
|
|
|
return kAddIceCandidateFailNoRemoteDescription;
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!ice_candidate) {
|
|
|
|
|
RTC_LOG(LS_ERROR) << "AddIceCandidate: Candidate is null.";
|
2021-04-01 16:26:57 +02:00
|
|
|
return kAddIceCandidateFailNullCandidate;
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool valid = false;
|
2020-10-05 13:08:41 +00:00
|
|
|
bool ready = ReadyToUseRemoteCandidate(ice_candidate, nullptr, &valid);
|
2020-09-28 13:02:07 +00:00
|
|
|
if (!valid) {
|
2021-04-01 16:26:57 +02:00
|
|
|
return kAddIceCandidateFailNotValid;
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add this candidate to the remote session description.
|
|
|
|
|
if (!mutable_remote_description()->AddCandidate(ice_candidate)) {
|
|
|
|
|
RTC_LOG(LS_ERROR) << "AddIceCandidate: Candidate cannot be used.";
|
2021-04-01 16:26:57 +02:00
|
|
|
return kAddIceCandidateFailInAddition;
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
|
2021-04-01 16:26:57 +02:00
|
|
|
if (!ready) {
|
2020-09-28 13:02:07 +00:00
|
|
|
RTC_LOG(LS_INFO) << "AddIceCandidate: Not ready to use candidate.";
|
2021-04-01 16:26:57 +02:00
|
|
|
return kAddIceCandidateFailNotReady;
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
2021-04-01 16:26:57 +02:00
|
|
|
|
|
|
|
|
if (!UseCandidate(ice_candidate)) {
|
|
|
|
|
return kAddIceCandidateFailNotUsable;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pc_->NoteUsageEvent(UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED);
|
|
|
|
|
|
|
|
|
|
return kAddIceCandidateSuccess;
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::AddIceCandidate(
|
|
|
|
|
std::unique_ptr<IceCandidateInterface> candidate,
|
|
|
|
|
std::function<void(RTCError)> callback) {
|
2021-06-07 13:30:46 +02:00
|
|
|
TRACE_EVENT0("webrtc", "SdpOfferAnswerHandler::AddIceCandidate");
|
2020-09-28 13:02:07 +00:00
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
2022-01-12 13:11:04 +01:00
|
|
|
// Chain this operation. If asynchronous operations are pending on the
|
|
|
|
|
// chain, this operation will be queued to be invoked, otherwise the
|
|
|
|
|
// contents of the lambda will execute immediately.
|
2020-09-28 13:02:07 +00:00
|
|
|
operations_chain_->ChainOperation(
|
|
|
|
|
[this_weak_ptr = weak_ptr_factory_.GetWeakPtr(),
|
|
|
|
|
candidate = std::move(candidate), callback = std::move(callback)](
|
|
|
|
|
std::function<void()> operations_chain_callback) {
|
2021-04-01 16:26:57 +02:00
|
|
|
auto result =
|
|
|
|
|
this_weak_ptr
|
|
|
|
|
? this_weak_ptr->AddIceCandidateInternal(candidate.get())
|
|
|
|
|
: kAddIceCandidateFailClosed;
|
|
|
|
|
NoteAddIceCandidateResult(result);
|
|
|
|
|
operations_chain_callback();
|
2022-01-21 15:18:08 +01:00
|
|
|
switch (result) {
|
|
|
|
|
case AddIceCandidateResult::kAddIceCandidateSuccess:
|
|
|
|
|
case AddIceCandidateResult::kAddIceCandidateFailNotReady:
|
|
|
|
|
// Success!
|
|
|
|
|
callback(RTCError::OK());
|
|
|
|
|
break;
|
|
|
|
|
case AddIceCandidateResult::kAddIceCandidateFailClosed:
|
|
|
|
|
// Note that the spec says to just abort without resolving the
|
|
|
|
|
// promise in this case, but this layer must return an RTCError.
|
|
|
|
|
callback(RTCError(
|
|
|
|
|
RTCErrorType::INVALID_STATE,
|
|
|
|
|
"AddIceCandidate failed because the session was shut down"));
|
|
|
|
|
break;
|
|
|
|
|
case AddIceCandidateResult::kAddIceCandidateFailNoRemoteDescription:
|
|
|
|
|
// Spec: "If remoteDescription is null return a promise rejected
|
|
|
|
|
// with a newly created InvalidStateError."
|
|
|
|
|
callback(RTCError(RTCErrorType::INVALID_STATE,
|
|
|
|
|
"The remote description was null"));
|
|
|
|
|
break;
|
|
|
|
|
case AddIceCandidateResult::kAddIceCandidateFailNullCandidate:
|
|
|
|
|
// TODO(https://crbug.com/935898): Handle end-of-candidates instead
|
|
|
|
|
// of treating null candidate as an error.
|
|
|
|
|
callback(RTCError(RTCErrorType::UNSUPPORTED_OPERATION,
|
|
|
|
|
"Error processing ICE candidate"));
|
|
|
|
|
break;
|
|
|
|
|
case AddIceCandidateResult::kAddIceCandidateFailNotValid:
|
|
|
|
|
case AddIceCandidateResult::kAddIceCandidateFailInAddition:
|
|
|
|
|
case AddIceCandidateResult::kAddIceCandidateFailNotUsable:
|
|
|
|
|
// Spec: "If candidate could not be successfully added [...] Reject
|
|
|
|
|
// p with a newly created OperationError and abort these steps."
|
|
|
|
|
// UNSUPPORTED_OPERATION maps to OperationError.
|
|
|
|
|
callback(RTCError(RTCErrorType::UNSUPPORTED_OPERATION,
|
|
|
|
|
"Error processing ICE candidate"));
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
RTC_DCHECK_NOTREACHED();
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool SdpOfferAnswerHandler::RemoveIceCandidates(
|
|
|
|
|
const std::vector<cricket::Candidate>& candidates) {
|
|
|
|
|
TRACE_EVENT0("webrtc", "SdpOfferAnswerHandler::RemoveIceCandidates");
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
if (pc_->IsClosed()) {
|
|
|
|
|
RTC_LOG(LS_ERROR) << "RemoveIceCandidates: PeerConnection is closed.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!remote_description()) {
|
|
|
|
|
RTC_LOG(LS_ERROR) << "RemoveIceCandidates: ICE candidates can't be removed "
|
|
|
|
|
"without any remote session description.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (candidates.empty()) {
|
|
|
|
|
RTC_LOG(LS_ERROR) << "RemoveIceCandidates: candidates are empty.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t number_removed =
|
|
|
|
|
mutable_remote_description()->RemoveCandidates(candidates);
|
|
|
|
|
if (number_removed != candidates.size()) {
|
|
|
|
|
RTC_LOG(LS_ERROR)
|
|
|
|
|
<< "RemoveIceCandidates: Failed to remove candidates. Requested "
|
|
|
|
|
<< candidates.size() << " but only " << number_removed
|
|
|
|
|
<< " are removed.";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove the candidates from the transport controller.
|
2022-02-09 12:08:47 +00:00
|
|
|
RTCError error = transport_controller_s()->RemoveRemoteCandidates(candidates);
|
2020-09-28 13:02:07 +00:00
|
|
|
if (!error.ok()) {
|
|
|
|
|
RTC_LOG(LS_ERROR)
|
|
|
|
|
<< "RemoveIceCandidates: Error when removing remote candidates: "
|
|
|
|
|
<< error.message();
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::AddLocalIceCandidate(
|
|
|
|
|
const JsepIceCandidate* candidate) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
if (local_description()) {
|
|
|
|
|
mutable_local_description()->AddCandidate(candidate);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::RemoveLocalIceCandidates(
|
|
|
|
|
const std::vector<cricket::Candidate>& candidates) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
if (local_description()) {
|
|
|
|
|
mutable_local_description()->RemoveCandidates(candidates);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const SessionDescriptionInterface* SdpOfferAnswerHandler::local_description()
|
|
|
|
|
const {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
return pending_local_description_ ? pending_local_description_.get()
|
|
|
|
|
: current_local_description_.get();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const SessionDescriptionInterface* SdpOfferAnswerHandler::remote_description()
|
|
|
|
|
const {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
return pending_remote_description_ ? pending_remote_description_.get()
|
|
|
|
|
: current_remote_description_.get();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const SessionDescriptionInterface*
|
|
|
|
|
SdpOfferAnswerHandler::current_local_description() const {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
return current_local_description_.get();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const SessionDescriptionInterface*
|
|
|
|
|
SdpOfferAnswerHandler::current_remote_description() const {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
return current_remote_description_.get();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const SessionDescriptionInterface*
|
|
|
|
|
SdpOfferAnswerHandler::pending_local_description() const {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
return pending_local_description_.get();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const SessionDescriptionInterface*
|
|
|
|
|
SdpOfferAnswerHandler::pending_remote_description() const {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
return pending_remote_description_.get();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PeerConnectionInterface::SignalingState SdpOfferAnswerHandler::signaling_state()
|
|
|
|
|
const {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
return signaling_state_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::ChangeSignalingState(
|
|
|
|
|
PeerConnectionInterface::SignalingState signaling_state) {
|
2021-06-07 13:30:46 +02:00
|
|
|
TRACE_EVENT0("webrtc", "SdpOfferAnswerHandler::ChangeSignalingState");
|
2020-09-28 13:02:07 +00:00
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
if (signaling_state_ == signaling_state) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
RTC_LOG(LS_INFO) << "Session: " << pc_->session_id() << " Old state: "
|
2021-11-02 10:54:38 +00:00
|
|
|
<< PeerConnectionInterface::AsString(signaling_state_)
|
2020-09-28 13:02:07 +00:00
|
|
|
<< " New state: "
|
2021-11-02 10:54:38 +00:00
|
|
|
<< PeerConnectionInterface::AsString(signaling_state);
|
2020-09-28 13:02:07 +00:00
|
|
|
signaling_state_ = signaling_state;
|
|
|
|
|
pc_->Observer()->OnSignalingChange(signaling_state_);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RTCError SdpOfferAnswerHandler::UpdateSessionState(
|
|
|
|
|
SdpType type,
|
|
|
|
|
cricket::ContentSource source,
|
2021-04-26 21:04:26 +02:00
|
|
|
const cricket::SessionDescription* description,
|
|
|
|
|
const std::map<std::string, const cricket::ContentGroup*>&
|
|
|
|
|
bundle_groups_by_mid) {
|
2020-09-28 13:02:07 +00:00
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
|
2022-01-12 13:11:04 +01:00
|
|
|
// If there's already a pending error then no state transition should
|
|
|
|
|
// happen. But all call-sites should be verifying this before calling us!
|
2020-10-01 16:47:23 +00:00
|
|
|
RTC_DCHECK(session_error() == SessionError::kNone);
|
2020-09-28 13:02:07 +00:00
|
|
|
|
2021-01-25 13:44:55 -08:00
|
|
|
// If this is answer-ish we're ready to let media flow.
|
|
|
|
|
if (type == SdpType::kPrAnswer || type == SdpType::kAnswer) {
|
|
|
|
|
EnableSending();
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-28 13:02:07 +00:00
|
|
|
// Update the signaling state according to the specified state machine (see
|
|
|
|
|
// https://w3c.github.io/webrtc-pc/#rtcsignalingstate-enum).
|
|
|
|
|
if (type == SdpType::kOffer) {
|
|
|
|
|
ChangeSignalingState(source == cricket::CS_LOCAL
|
|
|
|
|
? PeerConnectionInterface::kHaveLocalOffer
|
|
|
|
|
: PeerConnectionInterface::kHaveRemoteOffer);
|
|
|
|
|
} else if (type == SdpType::kPrAnswer) {
|
|
|
|
|
ChangeSignalingState(source == cricket::CS_LOCAL
|
|
|
|
|
? PeerConnectionInterface::kHaveLocalPrAnswer
|
|
|
|
|
: PeerConnectionInterface::kHaveRemotePrAnswer);
|
|
|
|
|
} else {
|
|
|
|
|
RTC_DCHECK(type == SdpType::kAnswer);
|
|
|
|
|
ChangeSignalingState(PeerConnectionInterface::kStable);
|
2022-05-23 14:57:47 +00:00
|
|
|
if (ConfiguredForMedia()) {
|
|
|
|
|
transceivers()->DiscardStableStates();
|
|
|
|
|
}
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update internal objects according to the session description's media
|
|
|
|
|
// descriptions.
|
2021-05-04 14:59:38 +02:00
|
|
|
return PushdownMediaDescription(type, source, bundle_groups_by_mid);
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool SdpOfferAnswerHandler::ShouldFireNegotiationNeededEvent(
|
|
|
|
|
uint32_t event_id) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
// Plan B? Always fire to conform with useless legacy behavior.
|
|
|
|
|
if (!IsUnifiedPlan()) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
// The event ID has been invalidated. Either negotiation is no longer needed
|
|
|
|
|
// or a newer negotiation needed event has been generated.
|
|
|
|
|
if (event_id != negotiation_needed_event_id_) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// The chain is no longer empty, update negotiation needed when it becomes
|
|
|
|
|
// empty. This should generate a newer negotiation needed event, making this
|
|
|
|
|
// one obsolete.
|
|
|
|
|
if (!operations_chain_->IsEmpty()) {
|
|
|
|
|
// Since we just suppressed an event that would have been fired, if
|
2022-01-12 13:11:04 +01:00
|
|
|
// negotiation is still needed by the time the chain becomes empty again,
|
|
|
|
|
// we must make sure to generate another event if negotiation is needed
|
|
|
|
|
// then. This happens when `is_negotiation_needed_` goes from false to
|
|
|
|
|
// true, so we set it to false until UpdateNegotiationNeeded() is called.
|
2020-09-28 13:02:07 +00:00
|
|
|
is_negotiation_needed_ = false;
|
|
|
|
|
update_negotiation_needed_on_empty_chain_ = true;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// We must not fire if the signaling state is no longer "stable". If
|
|
|
|
|
// negotiation is still needed when we return to "stable", a new negotiation
|
|
|
|
|
// needed event will be generated, so this one can safely be suppressed.
|
|
|
|
|
if (signaling_state_ != PeerConnectionInterface::kStable) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// All checks have passed - please fire "negotiationneeded" now!
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-09 11:42:17 +00:00
|
|
|
rtc::scoped_refptr<StreamCollectionInterface>
|
|
|
|
|
SdpOfferAnswerHandler::local_streams() {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
RTC_CHECK(!IsUnifiedPlan()) << "local_streams is not available with Unified "
|
|
|
|
|
"Plan SdpSemantics. Please use GetSenders "
|
|
|
|
|
"instead.";
|
|
|
|
|
return local_streams_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rtc::scoped_refptr<StreamCollectionInterface>
|
|
|
|
|
SdpOfferAnswerHandler::remote_streams() {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
RTC_CHECK(!IsUnifiedPlan()) << "remote_streams is not available with Unified "
|
|
|
|
|
"Plan SdpSemantics. Please use GetReceivers "
|
|
|
|
|
"instead.";
|
|
|
|
|
return remote_streams_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool SdpOfferAnswerHandler::AddStream(MediaStreamInterface* local_stream) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
RTC_CHECK(!IsUnifiedPlan()) << "AddStream is not available with Unified Plan "
|
|
|
|
|
"SdpSemantics. Please use AddTrack instead.";
|
|
|
|
|
if (pc_->IsClosed()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2022-04-20 14:26:50 +02:00
|
|
|
if (!CanAddLocalMediaStream(local_streams_.get(), local_stream)) {
|
2020-10-09 11:42:17 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-04 15:20:03 +01:00
|
|
|
local_streams_->AddStream(
|
|
|
|
|
rtc::scoped_refptr<MediaStreamInterface>(local_stream));
|
2021-08-02 10:56:33 +02:00
|
|
|
auto observer = std::make_unique<MediaStreamObserver>(
|
|
|
|
|
local_stream,
|
|
|
|
|
[this](AudioTrackInterface* audio_track,
|
|
|
|
|
MediaStreamInterface* media_stream) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
OnAudioTrackAdded(audio_track, media_stream);
|
|
|
|
|
},
|
|
|
|
|
[this](AudioTrackInterface* audio_track,
|
|
|
|
|
MediaStreamInterface* media_stream) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
OnAudioTrackRemoved(audio_track, media_stream);
|
|
|
|
|
},
|
|
|
|
|
[this](VideoTrackInterface* video_track,
|
|
|
|
|
MediaStreamInterface* media_stream) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
OnVideoTrackAdded(video_track, media_stream);
|
|
|
|
|
},
|
|
|
|
|
[this](VideoTrackInterface* video_track,
|
|
|
|
|
MediaStreamInterface* media_stream) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
OnVideoTrackRemoved(video_track, media_stream);
|
|
|
|
|
});
|
|
|
|
|
stream_observers_.push_back(std::move(observer));
|
2020-10-09 11:42:17 +00:00
|
|
|
|
|
|
|
|
for (const auto& track : local_stream->GetAudioTracks()) {
|
2020-10-19 13:28:05 +00:00
|
|
|
rtp_manager()->AddAudioTrack(track.get(), local_stream);
|
2020-10-09 11:42:17 +00:00
|
|
|
}
|
|
|
|
|
for (const auto& track : local_stream->GetVideoTracks()) {
|
2020-10-19 13:28:05 +00:00
|
|
|
rtp_manager()->AddVideoTrack(track.get(), local_stream);
|
2020-10-09 11:42:17 +00:00
|
|
|
}
|
|
|
|
|
|
2022-07-04 14:36:37 +02:00
|
|
|
pc_->legacy_stats()->AddStream(local_stream);
|
2020-10-09 11:42:17 +00:00
|
|
|
UpdateNegotiationNeeded();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::RemoveStream(MediaStreamInterface* local_stream) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
RTC_CHECK(!IsUnifiedPlan()) << "RemoveStream is not available with Unified "
|
|
|
|
|
"Plan SdpSemantics. Please use RemoveTrack "
|
|
|
|
|
"instead.";
|
|
|
|
|
TRACE_EVENT0("webrtc", "PeerConnection::RemoveStream");
|
|
|
|
|
if (!pc_->IsClosed()) {
|
|
|
|
|
for (const auto& track : local_stream->GetAudioTracks()) {
|
2020-10-19 13:28:05 +00:00
|
|
|
rtp_manager()->RemoveAudioTrack(track.get(), local_stream);
|
2020-10-09 11:42:17 +00:00
|
|
|
}
|
|
|
|
|
for (const auto& track : local_stream->GetVideoTracks()) {
|
2020-10-19 13:28:05 +00:00
|
|
|
rtp_manager()->RemoveVideoTrack(track.get(), local_stream);
|
2020-10-09 11:42:17 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
local_streams_->RemoveStream(local_stream);
|
|
|
|
|
stream_observers_.erase(
|
|
|
|
|
std::remove_if(
|
|
|
|
|
stream_observers_.begin(), stream_observers_.end(),
|
|
|
|
|
[local_stream](const std::unique_ptr<MediaStreamObserver>& observer) {
|
|
|
|
|
return observer->stream()->id().compare(local_stream->id()) == 0;
|
|
|
|
|
}),
|
|
|
|
|
stream_observers_.end());
|
|
|
|
|
|
|
|
|
|
if (pc_->IsClosed()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
UpdateNegotiationNeeded();
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-22 10:39:40 +00:00
|
|
|
void SdpOfferAnswerHandler::OnAudioTrackAdded(AudioTrackInterface* track,
|
|
|
|
|
MediaStreamInterface* stream) {
|
|
|
|
|
if (pc_->IsClosed()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
rtp_manager()->AddAudioTrack(track, stream);
|
|
|
|
|
UpdateNegotiationNeeded();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::OnAudioTrackRemoved(AudioTrackInterface* track,
|
|
|
|
|
MediaStreamInterface* stream) {
|
|
|
|
|
if (pc_->IsClosed()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
rtp_manager()->RemoveAudioTrack(track, stream);
|
|
|
|
|
UpdateNegotiationNeeded();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::OnVideoTrackAdded(VideoTrackInterface* track,
|
|
|
|
|
MediaStreamInterface* stream) {
|
|
|
|
|
if (pc_->IsClosed()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
rtp_manager()->AddVideoTrack(track, stream);
|
|
|
|
|
UpdateNegotiationNeeded();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::OnVideoTrackRemoved(VideoTrackInterface* track,
|
|
|
|
|
MediaStreamInterface* stream) {
|
|
|
|
|
if (pc_->IsClosed()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
rtp_manager()->RemoveVideoTrack(track, stream);
|
|
|
|
|
UpdateNegotiationNeeded();
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-28 13:02:07 +00:00
|
|
|
RTCError SdpOfferAnswerHandler::Rollback(SdpType desc_type) {
|
2021-06-07 13:30:46 +02:00
|
|
|
TRACE_EVENT0("webrtc", "SdpOfferAnswerHandler::Rollback");
|
2020-09-28 13:02:07 +00:00
|
|
|
auto state = signaling_state();
|
|
|
|
|
if (state != PeerConnectionInterface::kHaveLocalOffer &&
|
|
|
|
|
state != PeerConnectionInterface::kHaveRemoteOffer) {
|
2023-07-18 11:03:39 +02:00
|
|
|
LOG_AND_RETURN_ERROR(
|
|
|
|
|
RTCErrorType::INVALID_STATE,
|
|
|
|
|
(rtc::StringBuilder("Called in wrong signalingState: ")
|
|
|
|
|
<< (PeerConnectionInterface::AsString(signaling_state())))
|
|
|
|
|
.Release());
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
RTC_DCHECK(IsUnifiedPlan());
|
2022-05-02 15:47:52 +02:00
|
|
|
std::vector<rtc::scoped_refptr<RtpTransceiverInterface>>
|
|
|
|
|
now_receiving_transceivers;
|
2020-09-28 13:02:07 +00:00
|
|
|
std::vector<rtc::scoped_refptr<MediaStreamInterface>> all_added_streams;
|
|
|
|
|
std::vector<rtc::scoped_refptr<MediaStreamInterface>> all_removed_streams;
|
|
|
|
|
std::vector<rtc::scoped_refptr<RtpReceiverInterface>> removed_receivers;
|
|
|
|
|
|
2020-10-19 13:28:05 +00:00
|
|
|
for (auto&& transceivers_stable_state_pair : transceivers()->StableStates()) {
|
2020-09-28 13:02:07 +00:00
|
|
|
auto transceiver = transceivers_stable_state_pair.first;
|
|
|
|
|
auto state = transceivers_stable_state_pair.second;
|
|
|
|
|
|
2022-05-02 15:47:52 +02:00
|
|
|
if (state.did_set_fired_direction()) {
|
|
|
|
|
// If this rollback triggers going from not receiving to receving again,
|
|
|
|
|
// we need to fire "ontrack".
|
|
|
|
|
bool previously_fired_direction_is_recv =
|
|
|
|
|
transceiver->fired_direction().has_value() &&
|
|
|
|
|
RtpTransceiverDirectionHasRecv(*transceiver->fired_direction());
|
|
|
|
|
bool currently_fired_direction_is_recv =
|
|
|
|
|
state.fired_direction().has_value() &&
|
|
|
|
|
RtpTransceiverDirectionHasRecv(state.fired_direction().value());
|
|
|
|
|
if (!previously_fired_direction_is_recv &&
|
|
|
|
|
currently_fired_direction_is_recv) {
|
|
|
|
|
now_receiving_transceivers.push_back(transceiver);
|
|
|
|
|
}
|
|
|
|
|
transceiver->internal()->set_fired_direction(state.fired_direction());
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-28 13:02:07 +00:00
|
|
|
if (state.remote_stream_ids()) {
|
|
|
|
|
std::vector<rtc::scoped_refptr<MediaStreamInterface>> added_streams;
|
|
|
|
|
std::vector<rtc::scoped_refptr<MediaStreamInterface>> removed_streams;
|
|
|
|
|
SetAssociatedRemoteStreams(transceiver->internal()->receiver_internal(),
|
|
|
|
|
state.remote_stream_ids().value(),
|
|
|
|
|
&added_streams, &removed_streams);
|
|
|
|
|
all_added_streams.insert(all_added_streams.end(), added_streams.begin(),
|
|
|
|
|
added_streams.end());
|
|
|
|
|
all_removed_streams.insert(all_removed_streams.end(),
|
|
|
|
|
removed_streams.begin(),
|
|
|
|
|
removed_streams.end());
|
|
|
|
|
if (!state.has_m_section() && !state.newly_created()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-02 15:47:52 +02:00
|
|
|
// Due to the above `continue` statement, the below code only runs if there
|
|
|
|
|
// is a change in mid association (has_m_section), if the transceiver was
|
|
|
|
|
// newly created (newly_created) or if remote streams were not set.
|
|
|
|
|
|
2020-09-28 13:02:07 +00:00
|
|
|
RTC_DCHECK(transceiver->internal()->mid().has_value());
|
2022-04-28 13:31:17 +00:00
|
|
|
transceiver->internal()->ClearChannel();
|
2020-09-28 13:02:07 +00:00
|
|
|
|
|
|
|
|
if (signaling_state() == PeerConnectionInterface::kHaveRemoteOffer &&
|
|
|
|
|
transceiver->receiver()) {
|
|
|
|
|
removed_receivers.push_back(transceiver->receiver());
|
|
|
|
|
}
|
|
|
|
|
if (state.newly_created()) {
|
|
|
|
|
if (transceiver->internal()->reused_for_addtrack()) {
|
|
|
|
|
transceiver->internal()->set_created_by_addtrack(true);
|
|
|
|
|
} else {
|
[Rollback] Don't end tracks when transceiver is still in use.
Prior to this CL, calling RtpTransceiver::SetChannel() with null
arguments would cause the receiver's track to end. This is wrong,
because the channel can be nulled for other reasons than the transceiver
being stopped/removed - such as when the transceiver is rolled back but
still in use. Also, stopping a transceiver will end the track, so we
should simply ensure to always stop the transceiver when that is needed.
This CL makes sure that the transceiver is stopped or stopping in all
appropriate places, allowing us to remove the ability to end the source
for any other reason. A side-effect of this is that:
- The track never ends prematurely, fixing https://crbug.com/1315611.
- Removed transceivers are always stopped, fixing
https://crbug.com/webrtc/14005.
This CL fixes the issue of track being ended in the ontrack event when
running https://jsfiddle.net/henbos/nxebusjm/.
- We don't have WPT test coverage for this, so I'll add that separately.
With SetSourceEnded() removed, some stopping/stop in response to
rejecting locally SDP munged content had to be added in order not to
regress the existing test coverage for this:
*PeerConnectionInterfaceTest.RejectMediaContent/1
Bug: chromium:1315611, webrtc:14005.
Change-Id: I21f30a1259e51324066dc84f72a72485b9e0fadc
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/260180
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Henrik Boström <hbos@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#36669}
2022-04-27 11:54:01 +02:00
|
|
|
transceiver->internal()->StopTransceiverProcedure();
|
2020-10-19 13:28:05 +00:00
|
|
|
transceivers()->Remove(transceiver);
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
}
|
2021-04-06 22:38:00 +03:00
|
|
|
if (state.init_send_encodings()) {
|
|
|
|
|
transceiver->internal()->sender_internal()->set_init_send_encodings(
|
|
|
|
|
state.init_send_encodings().value());
|
|
|
|
|
}
|
2020-09-28 13:02:07 +00:00
|
|
|
transceiver->internal()->sender_internal()->set_transport(nullptr);
|
|
|
|
|
transceiver->internal()->receiver_internal()->set_transport(nullptr);
|
2023-03-15 16:47:43 +01:00
|
|
|
if (state.has_m_section()) {
|
|
|
|
|
transceiver->internal()->set_mid(state.mid());
|
|
|
|
|
transceiver->internal()->set_mline_index(state.mline_index());
|
|
|
|
|
}
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
2022-02-09 12:08:47 +00:00
|
|
|
RTCError e = transport_controller_s()->RollbackTransports();
|
2021-08-11 14:56:38 -07:00
|
|
|
if (!e.ok()) {
|
|
|
|
|
return e;
|
|
|
|
|
}
|
2020-10-19 13:28:05 +00:00
|
|
|
transceivers()->DiscardStableStates();
|
2020-09-28 13:02:07 +00:00
|
|
|
pending_local_description_.reset();
|
|
|
|
|
pending_remote_description_.reset();
|
|
|
|
|
ChangeSignalingState(PeerConnectionInterface::kStable);
|
|
|
|
|
|
|
|
|
|
// Once all processing has finished, fire off callbacks.
|
2022-05-02 15:47:52 +02:00
|
|
|
for (const auto& transceiver : now_receiving_transceivers) {
|
|
|
|
|
pc_->Observer()->OnTrack(transceiver);
|
|
|
|
|
pc_->Observer()->OnAddTrack(transceiver->receiver(),
|
|
|
|
|
transceiver->receiver()->streams());
|
|
|
|
|
}
|
2020-09-28 13:02:07 +00:00
|
|
|
for (const auto& receiver : removed_receivers) {
|
|
|
|
|
pc_->Observer()->OnRemoveTrack(receiver);
|
|
|
|
|
}
|
|
|
|
|
for (const auto& stream : all_added_streams) {
|
|
|
|
|
pc_->Observer()->OnAddStream(stream);
|
|
|
|
|
}
|
|
|
|
|
for (const auto& stream : all_removed_streams) {
|
|
|
|
|
pc_->Observer()->OnRemoveStream(stream);
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-12 13:11:04 +01:00
|
|
|
// The assumption is that in case of implicit rollback
|
|
|
|
|
// UpdateNegotiationNeeded gets called in SetRemoteDescription.
|
2020-09-28 13:02:07 +00:00
|
|
|
if (desc_type == SdpType::kRollback) {
|
|
|
|
|
UpdateNegotiationNeeded();
|
|
|
|
|
if (is_negotiation_needed_) {
|
|
|
|
|
// Legacy version.
|
|
|
|
|
pc_->Observer()->OnRenegotiationNeeded();
|
|
|
|
|
// Spec-compliant version; the event may get invalidated before firing.
|
|
|
|
|
GenerateNegotiationNeededEvent();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return RTCError::OK();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool SdpOfferAnswerHandler::IsUnifiedPlan() const {
|
|
|
|
|
return pc_->IsUnifiedPlan();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::OnOperationsChainEmpty() {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
if (pc_->IsClosed() || !update_negotiation_needed_on_empty_chain_)
|
|
|
|
|
return;
|
|
|
|
|
update_negotiation_needed_on_empty_chain_ = false;
|
2022-01-12 13:11:04 +01:00
|
|
|
// Firing when chain is empty is only supported in Unified Plan to avoid
|
|
|
|
|
// Plan B regressions. (In Plan B, onnegotiationneeded is already broken
|
|
|
|
|
// anyway, so firing it even more might just be confusing.)
|
2020-09-28 13:02:07 +00:00
|
|
|
if (IsUnifiedPlan()) {
|
|
|
|
|
UpdateNegotiationNeeded();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-29 13:00:40 +00:00
|
|
|
std::optional<bool> SdpOfferAnswerHandler::is_caller() const {
|
2020-09-28 13:02:07 +00:00
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
return is_caller_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool SdpOfferAnswerHandler::HasNewIceCredentials() {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
return local_ice_credentials_to_replace_->HasIceCredentials();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool SdpOfferAnswerHandler::IceRestartPending(
|
|
|
|
|
const std::string& content_name) const {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
return pending_ice_restarts_.find(content_name) !=
|
|
|
|
|
pending_ice_restarts_.end();
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-23 13:30:46 +00:00
|
|
|
bool SdpOfferAnswerHandler::NeedsIceRestart(
|
|
|
|
|
const std::string& content_name) const {
|
2021-02-10 17:40:08 +00:00
|
|
|
return pc_->NeedsIceRestart(content_name);
|
2020-10-23 13:30:46 +00:00
|
|
|
}
|
|
|
|
|
|
2024-08-29 13:00:40 +00:00
|
|
|
std::optional<rtc::SSLRole> SdpOfferAnswerHandler::GetDtlsRole(
|
2020-10-23 13:30:46 +00:00
|
|
|
const std::string& mid) const {
|
2022-02-09 12:08:47 +00:00
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
return transport_controller_s()->GetDtlsRole(mid);
|
2020-10-23 13:30:46 +00:00
|
|
|
}
|
|
|
|
|
|
2020-09-28 13:02:07 +00:00
|
|
|
void SdpOfferAnswerHandler::UpdateNegotiationNeeded() {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
if (!IsUnifiedPlan()) {
|
|
|
|
|
pc_->Observer()->OnRenegotiationNeeded();
|
|
|
|
|
GenerateNegotiationNeededEvent();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// In the spec, a task is queued here to run the following steps - this is
|
2022-01-12 13:11:04 +01:00
|
|
|
// meant to ensure we do not fire onnegotiationneeded prematurely if
|
|
|
|
|
// multiple changes are being made at once. In order to support Chromium's
|
2020-09-28 13:02:07 +00:00
|
|
|
// implementation where the JavaScript representation of the PeerConnection
|
|
|
|
|
// lives on a separate thread though, the queuing of a task is instead
|
|
|
|
|
// performed by the PeerConnectionObserver posting from the signaling thread
|
|
|
|
|
// to the JavaScript main thread that negotiation is needed. And because the
|
|
|
|
|
// Operations Chain lives on the WebRTC signaling thread,
|
|
|
|
|
// ShouldFireNegotiationNeededEvent() must be called before firing the event
|
|
|
|
|
// to ensure the Operations Chain is still empty and the event has not been
|
|
|
|
|
// invalidated.
|
|
|
|
|
|
|
|
|
|
// If connection's [[IsClosed]] slot is true, abort these steps.
|
|
|
|
|
if (pc_->IsClosed())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// If connection's signaling state is not "stable", abort these steps.
|
|
|
|
|
if (signaling_state() != PeerConnectionInterface::kStable)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// NOTE
|
|
|
|
|
// The negotiation-needed flag will be updated once the state transitions to
|
|
|
|
|
// "stable", as part of the steps for setting an RTCSessionDescription.
|
|
|
|
|
|
|
|
|
|
// If the result of checking if negotiation is needed is false, clear the
|
2022-01-12 13:11:04 +01:00
|
|
|
// negotiation-needed flag by setting connection's [[NegotiationNeeded]]
|
|
|
|
|
// slot to false, and abort these steps.
|
2020-09-28 13:02:07 +00:00
|
|
|
bool is_negotiation_needed = CheckIfNegotiationIsNeeded();
|
|
|
|
|
if (!is_negotiation_needed) {
|
|
|
|
|
is_negotiation_needed_ = false;
|
|
|
|
|
// Invalidate any negotiation needed event that may previosuly have been
|
|
|
|
|
// generated.
|
|
|
|
|
++negotiation_needed_event_id_;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If connection's [[NegotiationNeeded]] slot is already true, abort these
|
|
|
|
|
// steps.
|
|
|
|
|
if (is_negotiation_needed_)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Set connection's [[NegotiationNeeded]] slot to true.
|
|
|
|
|
is_negotiation_needed_ = true;
|
|
|
|
|
|
|
|
|
|
// Queue a task that runs the following steps:
|
|
|
|
|
// If connection's [[IsClosed]] slot is true, abort these steps.
|
|
|
|
|
// If connection's [[NegotiationNeeded]] slot is false, abort these steps.
|
|
|
|
|
// Fire an event named negotiationneeded at connection.
|
|
|
|
|
pc_->Observer()->OnRenegotiationNeeded();
|
2022-01-12 13:11:04 +01:00
|
|
|
// Fire the spec-compliant version; when ShouldFireNegotiationNeededEvent()
|
|
|
|
|
// is used in the task queued by the observer, this event will only fire
|
|
|
|
|
// when the chain is empty.
|
2020-09-28 13:02:07 +00:00
|
|
|
GenerateNegotiationNeededEvent();
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-22 08:25:38 +01:00
|
|
|
void SdpOfferAnswerHandler::AllocateSctpSids() {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
if (!local_description() || !remote_description()) {
|
|
|
|
|
RTC_DLOG(LS_VERBOSE)
|
|
|
|
|
<< "Local and Remote descriptions must be applied to get the "
|
|
|
|
|
"SSL Role of the SCTP transport.";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-29 13:00:40 +00:00
|
|
|
std::optional<rtc::SSLRole> guessed_role = GuessSslRole();
|
2023-04-06 21:21:45 +02:00
|
|
|
network_thread()->BlockingCall(
|
|
|
|
|
[&, data_channel_controller = data_channel_controller()] {
|
|
|
|
|
RTC_DCHECK_RUN_ON(network_thread());
|
2024-08-29 13:00:40 +00:00
|
|
|
std::optional<rtc::SSLRole> role = pc_->GetSctpSslRole_n();
|
2023-04-06 21:21:45 +02:00
|
|
|
if (!role)
|
|
|
|
|
role = guessed_role;
|
|
|
|
|
if (role)
|
|
|
|
|
data_channel_controller->AllocateSctpSids(*role);
|
|
|
|
|
});
|
2023-03-22 08:25:38 +01:00
|
|
|
}
|
|
|
|
|
|
2024-08-29 13:00:40 +00:00
|
|
|
std::optional<rtc::SSLRole> SdpOfferAnswerHandler::GuessSslRole() const {
|
2023-03-25 10:56:18 +01:00
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
if (!pc_->sctp_mid())
|
2024-08-29 13:00:40 +00:00
|
|
|
return std::nullopt;
|
2023-03-25 10:56:18 +01:00
|
|
|
|
|
|
|
|
// TODO(bugs.webrtc.org/13668): This guesswork is guessing wrong (returning
|
|
|
|
|
// SSL_CLIENT = ACTIVE) if remote offer has role ACTIVE, but we'll be able
|
|
|
|
|
// to detect that by looking at the SDP.
|
|
|
|
|
//
|
|
|
|
|
// The phases of establishing an SCTP session are:
|
|
|
|
|
//
|
|
|
|
|
// Offerer:
|
|
|
|
|
//
|
|
|
|
|
// * Before negotiation: Neither is_caller nor sctp_mid exists.
|
|
|
|
|
// * After setting an offer as local description: is_caller is known (true),
|
|
|
|
|
// sctp_mid is known, but we don't know the SSL role for sure (or if we'll
|
|
|
|
|
// eventually get an SCTP session).
|
|
|
|
|
// * After setting an answer as the remote description: We know is_caller,
|
|
|
|
|
// sctp_mid and that we'll get the SCTP channel established (m-section
|
|
|
|
|
// wasn't rejected).
|
|
|
|
|
// * Special case: The SCTP m-section was rejected: Close datachannels.
|
|
|
|
|
// * We MAY know the SSL role if we offered actpass and got back active or
|
|
|
|
|
// passive; if the other end is a webrtc implementation, it will be active.
|
|
|
|
|
// * After the TLS handshake: We have a definitive answer on the SSL role.
|
|
|
|
|
//
|
|
|
|
|
// Answerer:
|
|
|
|
|
//
|
|
|
|
|
// * After setting an offer as remote description: We know is_caller (false).
|
|
|
|
|
// * If there was an SCTP session, we know the SCTP mid. We also know the
|
|
|
|
|
// SSL role, since if the remote offer was actpass or passive, we'll answer
|
|
|
|
|
// active, and if the remote offer was active, we're passive.
|
|
|
|
|
// * Special case: No SCTP m= line. We don't know for sure if the remote
|
|
|
|
|
// doesn't support it or just didn't offer it. Not sure what we do in this
|
|
|
|
|
// case (logic would suggest fire a `negotiationneeded` event and generate a
|
|
|
|
|
// subsequent offer, but this needs to be tested).
|
|
|
|
|
// * After the TLS handshake: We know that TLS obeyed the protocol. There
|
|
|
|
|
// should be an error surfaced somewhere if it didn't.
|
|
|
|
|
// * "Guessing" should always be correct if we get an SCTP session and are not
|
|
|
|
|
// the offerer.
|
|
|
|
|
|
|
|
|
|
return is_caller() ? rtc::SSL_SERVER : rtc::SSL_CLIENT;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-28 13:02:07 +00:00
|
|
|
bool SdpOfferAnswerHandler::CheckIfNegotiationIsNeeded() {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
2022-01-12 13:11:04 +01:00
|
|
|
// 1. If any implementation-specific negotiation is required, as described
|
|
|
|
|
// at the start of this section, return true.
|
2020-09-28 13:02:07 +00:00
|
|
|
|
|
|
|
|
// 2. If connection.[[LocalIceCredentialsToReplace]] is not empty, return
|
|
|
|
|
// true.
|
|
|
|
|
if (local_ice_credentials_to_replace_->HasIceCredentials()) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. Let description be connection.[[CurrentLocalDescription]].
|
|
|
|
|
const SessionDescriptionInterface* description = current_local_description();
|
|
|
|
|
if (!description)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
// 4. If connection has created any RTCDataChannels, and no m= section in
|
|
|
|
|
// description has been negotiated yet for data, return true.
|
2023-03-30 01:09:45 +02:00
|
|
|
if (data_channel_controller()->HasUsedDataChannels()) {
|
2023-05-09 09:41:03 +02:00
|
|
|
const cricket::ContentInfo* data_content =
|
|
|
|
|
cricket::GetFirstDataContent(description->description()->contents());
|
|
|
|
|
if (!data_content) {
|
2020-09-28 13:02:07 +00:00
|
|
|
return true;
|
2023-05-09 09:41:03 +02:00
|
|
|
}
|
|
|
|
|
// The remote end might have rejected the data content.
|
|
|
|
|
const cricket::ContentInfo* remote_data_content =
|
|
|
|
|
current_remote_description()
|
|
|
|
|
? current_remote_description()->description()->GetContentByName(
|
|
|
|
|
data_content->name)
|
|
|
|
|
: nullptr;
|
|
|
|
|
if (remote_data_content && remote_data_content->rejected) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
2022-05-23 14:57:47 +00:00
|
|
|
if (!ConfiguredForMedia()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-09-28 13:02:07 +00:00
|
|
|
|
|
|
|
|
// 5. For each transceiver in connection's set of transceivers, perform the
|
|
|
|
|
// following checks:
|
2021-04-19 21:21:36 +00:00
|
|
|
for (const auto& transceiver : transceivers()->ListInternal()) {
|
2020-09-28 13:02:07 +00:00
|
|
|
const ContentInfo* current_local_msection =
|
2021-04-19 21:21:36 +00:00
|
|
|
FindTransceiverMSection(transceiver, description);
|
2020-09-28 13:02:07 +00:00
|
|
|
|
2021-04-19 21:21:36 +00:00
|
|
|
const ContentInfo* current_remote_msection =
|
|
|
|
|
FindTransceiverMSection(transceiver, current_remote_description());
|
2020-09-28 13:02:07 +00:00
|
|
|
|
|
|
|
|
// 5.4 If transceiver is stopped and is associated with an m= section,
|
|
|
|
|
// but the associated m= section is not yet rejected in
|
|
|
|
|
// connection.[[CurrentLocalDescription]] or
|
|
|
|
|
// connection.[[CurrentRemoteDescription]], return true.
|
|
|
|
|
if (transceiver->stopped()) {
|
|
|
|
|
RTC_DCHECK(transceiver->stopping());
|
|
|
|
|
if (current_local_msection && !current_local_msection->rejected &&
|
|
|
|
|
((current_remote_msection && !current_remote_msection->rejected) ||
|
|
|
|
|
!current_remote_msection)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 5.1 If transceiver.[[Stopping]] is true and transceiver.[[Stopped]] is
|
|
|
|
|
// false, return true.
|
|
|
|
|
if (transceiver->stopping() && !transceiver->stopped())
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
// 5.2 If transceiver isn't stopped and isn't yet associated with an m=
|
|
|
|
|
// section in description, return true.
|
|
|
|
|
if (!current_local_msection)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
const MediaContentDescription* current_local_media_description =
|
|
|
|
|
current_local_msection->media_description();
|
|
|
|
|
// 5.3 If transceiver isn't stopped and is associated with an m= section
|
|
|
|
|
// in description then perform the following checks:
|
|
|
|
|
|
|
|
|
|
// 5.3.1 If transceiver.[[Direction]] is "sendrecv" or "sendonly", and the
|
|
|
|
|
// associated m= section in description either doesn't contain a single
|
|
|
|
|
// "a=msid" line, or the number of MSIDs from the "a=msid" lines in this
|
|
|
|
|
// m= section, or the MSID values themselves, differ from what is in
|
|
|
|
|
// transceiver.sender.[[AssociatedMediaStreamIds]], return true.
|
|
|
|
|
if (RtpTransceiverDirectionHasSend(transceiver->direction())) {
|
|
|
|
|
if (current_local_media_description->streams().size() == 0)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
std::vector<std::string> msection_msids;
|
|
|
|
|
for (const auto& stream : current_local_media_description->streams()) {
|
|
|
|
|
for (const std::string& msid : stream.stream_ids())
|
|
|
|
|
msection_msids.push_back(msid);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<std::string> transceiver_msids =
|
|
|
|
|
transceiver->sender()->stream_ids();
|
|
|
|
|
if (msection_msids.size() != transceiver_msids.size())
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
absl::c_sort(transceiver_msids);
|
|
|
|
|
absl::c_sort(msection_msids);
|
|
|
|
|
if (transceiver_msids != msection_msids)
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 5.3.2 If description is of type "offer", and the direction of the
|
|
|
|
|
// associated m= section in neither connection.[[CurrentLocalDescription]]
|
|
|
|
|
// nor connection.[[CurrentRemoteDescription]] matches
|
|
|
|
|
// transceiver.[[Direction]], return true.
|
|
|
|
|
if (description->GetType() == SdpType::kOffer) {
|
|
|
|
|
if (!current_remote_description())
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
if (!current_remote_msection)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
RtpTransceiverDirection current_local_direction =
|
|
|
|
|
current_local_media_description->direction();
|
|
|
|
|
RtpTransceiverDirection current_remote_direction =
|
|
|
|
|
current_remote_msection->media_description()->direction();
|
|
|
|
|
if (transceiver->direction() != current_local_direction &&
|
|
|
|
|
transceiver->direction() !=
|
|
|
|
|
RtpTransceiverDirectionReversed(current_remote_direction)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 5.3.3 If description is of type "answer", and the direction of the
|
|
|
|
|
// associated m= section in the description does not match
|
|
|
|
|
// transceiver.[[Direction]] intersected with the offered direction (as
|
|
|
|
|
// described in [JSEP] (section 5.3.1.)), return true.
|
|
|
|
|
if (description->GetType() == SdpType::kAnswer) {
|
|
|
|
|
if (!remote_description())
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
const ContentInfo* offered_remote_msection =
|
2021-04-19 21:21:36 +00:00
|
|
|
FindTransceiverMSection(transceiver, remote_description());
|
2020-09-28 13:02:07 +00:00
|
|
|
|
|
|
|
|
RtpTransceiverDirection offered_direction =
|
|
|
|
|
offered_remote_msection
|
|
|
|
|
? offered_remote_msection->media_description()->direction()
|
|
|
|
|
: RtpTransceiverDirection::kInactive;
|
|
|
|
|
|
|
|
|
|
if (current_local_media_description->direction() !=
|
|
|
|
|
(RtpTransceiverDirectionIntersection(
|
|
|
|
|
transceiver->direction(),
|
|
|
|
|
RtpTransceiverDirectionReversed(offered_direction)))) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// If all the preceding checks were performed and true was not returned,
|
|
|
|
|
// nothing remains to be negotiated; return false.
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::GenerateNegotiationNeededEvent() {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
++negotiation_needed_event_id_;
|
|
|
|
|
pc_->Observer()->OnNegotiationNeededEvent(negotiation_needed_event_id_);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RTCError SdpOfferAnswerHandler::ValidateSessionDescription(
|
|
|
|
|
const SessionDescriptionInterface* sdesc,
|
2021-04-26 21:04:26 +02:00
|
|
|
cricket::ContentSource source,
|
|
|
|
|
const std::map<std::string, const cricket::ContentGroup*>&
|
|
|
|
|
bundle_groups_by_mid) {
|
2022-01-12 13:11:04 +01:00
|
|
|
// An assumption is that a check for session error is done at a higher level.
|
|
|
|
|
RTC_DCHECK_EQ(SessionError::kNone, session_error());
|
2020-09-28 13:02:07 +00:00
|
|
|
|
|
|
|
|
if (!sdesc || !sdesc->description()) {
|
2023-07-18 11:03:39 +02:00
|
|
|
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, kInvalidSdp);
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SdpType type = sdesc->GetType();
|
2020-10-01 10:23:33 +00:00
|
|
|
if ((source == cricket::CS_LOCAL && !ExpectSetLocalDescription(type)) ||
|
|
|
|
|
(source == cricket::CS_REMOTE && !ExpectSetRemoteDescription(type))) {
|
2023-07-18 11:03:39 +02:00
|
|
|
LOG_AND_RETURN_ERROR(
|
|
|
|
|
RTCErrorType::INVALID_STATE,
|
|
|
|
|
(rtc::StringBuilder("Called in wrong state: ")
|
|
|
|
|
<< PeerConnectionInterface::AsString(signaling_state()))
|
|
|
|
|
.Release());
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RTCError error = ValidateMids(*sdesc->description());
|
|
|
|
|
if (!error.ok()) {
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify crypto settings.
|
|
|
|
|
std::string crypto_error;
|
2024-02-08 13:15:51 +00:00
|
|
|
if (pc_->dtls_enabled()) {
|
2021-11-04 13:52:31 +00:00
|
|
|
RTCError crypto_error = VerifyCrypto(
|
|
|
|
|
sdesc->description(), pc_->dtls_enabled(), bundle_groups_by_mid);
|
2020-09-28 13:02:07 +00:00
|
|
|
if (!crypto_error.ok()) {
|
|
|
|
|
return crypto_error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify ice-ufrag and ice-pwd.
|
2021-04-26 21:04:26 +02:00
|
|
|
if (!VerifyIceUfragPwdPresent(sdesc->description(), bundle_groups_by_mid)) {
|
2023-07-18 11:03:39 +02:00
|
|
|
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
|
|
|
|
|
kSdpWithoutIceUfragPwd);
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
|
2022-12-19 11:04:06 +01:00
|
|
|
// Validate that there are no collisions of bundled payload types.
|
2022-11-28 17:48:04 +01:00
|
|
|
error = ValidateBundledPayloadTypes(*sdesc->description());
|
|
|
|
|
// TODO(bugs.webrtc.org/14420): actually reject.
|
|
|
|
|
RTC_HISTOGRAM_BOOLEAN("WebRTC.PeerConnection.ValidBundledPayloadTypes",
|
|
|
|
|
error.ok());
|
|
|
|
|
|
2022-12-19 11:04:06 +01:00
|
|
|
// Validate that there are no collisions of bundled header extensions ids.
|
|
|
|
|
error = ValidateBundledRtpHeaderExtensions(*sdesc->description());
|
2023-08-22 16:05:38 +02:00
|
|
|
if (!error.ok()) {
|
2023-05-25 16:11:36 +02:00
|
|
|
return error;
|
|
|
|
|
}
|
2022-12-19 11:04:06 +01:00
|
|
|
|
2024-04-23 10:29:54 -07:00
|
|
|
// Validate the SSRC groups.
|
2023-08-11 09:32:50 +02:00
|
|
|
error = ValidateSsrcGroups(*sdesc->description());
|
2024-04-23 10:29:54 -07:00
|
|
|
if (!error.ok()) {
|
2023-08-11 09:32:50 +02:00
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-26 21:04:26 +02:00
|
|
|
if (!pc_->ValidateBundleSettings(sdesc->description(),
|
|
|
|
|
bundle_groups_by_mid)) {
|
2023-07-18 11:03:39 +02:00
|
|
|
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
|
|
|
|
|
kBundleWithoutRtcpMux);
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
|
2023-09-14 16:23:31 +02:00
|
|
|
error = ValidatePayloadTypes(*sdesc->description());
|
|
|
|
|
if (!error.ok()) {
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-28 13:02:07 +00:00
|
|
|
// TODO(skvlad): When the local rtcp-mux policy is Require, reject any
|
|
|
|
|
// m-lines that do not rtcp-mux enabled.
|
|
|
|
|
|
|
|
|
|
// Verify m-lines in Answer when compared against Offer.
|
|
|
|
|
if (type == SdpType::kPrAnswer || type == SdpType::kAnswer) {
|
2022-01-12 13:11:04 +01:00
|
|
|
// With an answer we want to compare the new answer session description
|
|
|
|
|
// with the offer's session description from the current negotiation.
|
2020-09-28 13:02:07 +00:00
|
|
|
const cricket::SessionDescription* offer_desc =
|
|
|
|
|
(source == cricket::CS_LOCAL) ? remote_description()->description()
|
|
|
|
|
: local_description()->description();
|
|
|
|
|
if (!MediaSectionsHaveSameCount(*offer_desc, *sdesc->description()) ||
|
|
|
|
|
!MediaSectionsInSameOrder(*offer_desc, nullptr, *sdesc->description(),
|
|
|
|
|
type)) {
|
2023-07-18 11:03:39 +02:00
|
|
|
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
|
|
|
|
|
kMlineMismatchInAnswer);
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// The re-offers should respect the order of m= sections in current
|
|
|
|
|
// description. See RFC3264 Section 8 paragraph 4 for more details.
|
2022-01-12 13:11:04 +01:00
|
|
|
// With a re-offer, either the current local or current remote
|
|
|
|
|
// descriptions could be the most up to date, so we would like to check
|
|
|
|
|
// against both of them if they exist. It could be the case that one of
|
|
|
|
|
// them has a 0 port for a media section, but the other does not. This is
|
|
|
|
|
// important to check against in the case that we are recycling an m=
|
|
|
|
|
// section.
|
2020-09-28 13:02:07 +00:00
|
|
|
const cricket::SessionDescription* current_desc = nullptr;
|
|
|
|
|
const cricket::SessionDescription* secondary_current_desc = nullptr;
|
|
|
|
|
if (local_description()) {
|
|
|
|
|
current_desc = local_description()->description();
|
|
|
|
|
if (remote_description()) {
|
|
|
|
|
secondary_current_desc = remote_description()->description();
|
|
|
|
|
}
|
|
|
|
|
} else if (remote_description()) {
|
|
|
|
|
current_desc = remote_description()->description();
|
|
|
|
|
}
|
|
|
|
|
if (current_desc &&
|
|
|
|
|
!MediaSectionsInSameOrder(*current_desc, secondary_current_desc,
|
|
|
|
|
*sdesc->description(), type)) {
|
2023-07-18 11:03:39 +02:00
|
|
|
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
|
|
|
|
|
kMlineMismatchInSubsequentOffer);
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (IsUnifiedPlan()) {
|
|
|
|
|
// Ensure that each audio and video media section has at most one
|
|
|
|
|
// "StreamParams". This will return an error if receiving a session
|
|
|
|
|
// description from a "Plan B" endpoint which adds multiple tracks of the
|
|
|
|
|
// same type. With Unified Plan, there can only be at most one track per
|
|
|
|
|
// media section.
|
|
|
|
|
for (const ContentInfo& content : sdesc->description()->contents()) {
|
|
|
|
|
const MediaContentDescription& desc = *content.media_description();
|
|
|
|
|
if ((desc.type() == cricket::MEDIA_TYPE_AUDIO ||
|
|
|
|
|
desc.type() == cricket::MEDIA_TYPE_VIDEO) &&
|
|
|
|
|
desc.streams().size() > 1u) {
|
2023-07-18 11:03:39 +02:00
|
|
|
LOG_AND_RETURN_ERROR(
|
2022-01-17 19:19:56 +01:00
|
|
|
RTCErrorType::INVALID_PARAMETER,
|
|
|
|
|
"Media section has more than one track specified with a=ssrc lines "
|
|
|
|
|
"which is not supported with Unified Plan.");
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
}
|
2023-06-01 19:07:50 +02:00
|
|
|
// Validate spec-simulcast which only works if the remote end negotiated the
|
|
|
|
|
// mid and rid header extension.
|
|
|
|
|
error = ValidateRtpHeaderExtensionsForSpecSimulcast(*sdesc->description());
|
|
|
|
|
if (!error.ok()) {
|
|
|
|
|
return error;
|
|
|
|
|
}
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return RTCError::OK();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RTCError SdpOfferAnswerHandler::UpdateTransceiversAndDataChannels(
|
|
|
|
|
cricket::ContentSource source,
|
|
|
|
|
const SessionDescriptionInterface& new_session,
|
|
|
|
|
const SessionDescriptionInterface* old_local_description,
|
2021-04-26 21:04:26 +02:00
|
|
|
const SessionDescriptionInterface* old_remote_description,
|
|
|
|
|
const std::map<std::string, const cricket::ContentGroup*>&
|
|
|
|
|
bundle_groups_by_mid) {
|
2021-06-07 13:30:46 +02:00
|
|
|
TRACE_EVENT0("webrtc",
|
|
|
|
|
"SdpOfferAnswerHandler::UpdateTransceiversAndDataChannels");
|
2020-09-28 13:02:07 +00:00
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
RTC_DCHECK(IsUnifiedPlan());
|
|
|
|
|
|
|
|
|
|
if (new_session.GetType() == SdpType::kOffer) {
|
2021-04-26 21:04:26 +02:00
|
|
|
// If the BUNDLE policy is max-bundle, then we know for sure that all
|
2022-01-12 13:11:04 +01:00
|
|
|
// transports will be bundled from the start. Return an error if
|
|
|
|
|
// max-bundle is specified but the session description does not have a
|
|
|
|
|
// BUNDLE group.
|
2021-04-26 21:04:26 +02:00
|
|
|
if (pc_->configuration()->bundle_policy ==
|
|
|
|
|
PeerConnectionInterface::kBundlePolicyMaxBundle &&
|
|
|
|
|
bundle_groups_by_mid.empty()) {
|
2023-07-18 11:03:39 +02:00
|
|
|
LOG_AND_RETURN_ERROR(
|
2022-01-17 19:19:56 +01:00
|
|
|
RTCErrorType::INVALID_PARAMETER,
|
|
|
|
|
"max-bundle configured but session description has no BUNDLE group");
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ContentInfos& new_contents = new_session.description()->contents();
|
|
|
|
|
for (size_t i = 0; i < new_contents.size(); ++i) {
|
|
|
|
|
const cricket::ContentInfo& new_content = new_contents[i];
|
|
|
|
|
cricket::MediaType media_type = new_content.media_description()->type();
|
2020-10-05 13:08:41 +00:00
|
|
|
mid_generator_.AddKnownId(new_content.name);
|
2021-04-26 21:04:26 +02:00
|
|
|
auto it = bundle_groups_by_mid.find(new_content.name);
|
|
|
|
|
const cricket::ContentGroup* bundle_group =
|
|
|
|
|
it != bundle_groups_by_mid.end() ? it->second : nullptr;
|
2020-09-28 13:02:07 +00:00
|
|
|
if (media_type == cricket::MEDIA_TYPE_AUDIO ||
|
|
|
|
|
media_type == cricket::MEDIA_TYPE_VIDEO) {
|
|
|
|
|
const cricket::ContentInfo* old_local_content = nullptr;
|
|
|
|
|
if (old_local_description &&
|
|
|
|
|
i < old_local_description->description()->contents().size()) {
|
|
|
|
|
old_local_content =
|
|
|
|
|
&old_local_description->description()->contents()[i];
|
|
|
|
|
}
|
|
|
|
|
const cricket::ContentInfo* old_remote_content = nullptr;
|
|
|
|
|
if (old_remote_description &&
|
|
|
|
|
i < old_remote_description->description()->contents().size()) {
|
|
|
|
|
old_remote_content =
|
|
|
|
|
&old_remote_description->description()->contents()[i];
|
|
|
|
|
}
|
|
|
|
|
auto transceiver_or_error =
|
|
|
|
|
AssociateTransceiver(source, new_session.GetType(), i, new_content,
|
|
|
|
|
old_local_content, old_remote_content);
|
|
|
|
|
if (!transceiver_or_error.ok()) {
|
[Rollback] Don't end tracks when transceiver is still in use.
Prior to this CL, calling RtpTransceiver::SetChannel() with null
arguments would cause the receiver's track to end. This is wrong,
because the channel can be nulled for other reasons than the transceiver
being stopped/removed - such as when the transceiver is rolled back but
still in use. Also, stopping a transceiver will end the track, so we
should simply ensure to always stop the transceiver when that is needed.
This CL makes sure that the transceiver is stopped or stopping in all
appropriate places, allowing us to remove the ability to end the source
for any other reason. A side-effect of this is that:
- The track never ends prematurely, fixing https://crbug.com/1315611.
- Removed transceivers are always stopped, fixing
https://crbug.com/webrtc/14005.
This CL fixes the issue of track being ended in the ontrack event when
running https://jsfiddle.net/henbos/nxebusjm/.
- We don't have WPT test coverage for this, so I'll add that separately.
With SetSourceEnded() removed, some stopping/stop in response to
rejecting locally SDP munged content had to be added in order not to
regress the existing test coverage for this:
*PeerConnectionInterfaceTest.RejectMediaContent/1
Bug: chromium:1315611, webrtc:14005.
Change-Id: I21f30a1259e51324066dc84f72a72485b9e0fadc
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/260180
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Henrik Boström <hbos@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#36669}
2022-04-27 11:54:01 +02:00
|
|
|
// In the case where a transceiver is rejected locally prior to being
|
|
|
|
|
// associated, we don't expect to find a transceiver, but might find it
|
|
|
|
|
// in the case where state is still "stopping", not "stopped".
|
2020-10-09 08:13:30 +00:00
|
|
|
if (new_content.rejected) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2020-09-28 13:02:07 +00:00
|
|
|
return transceiver_or_error.MoveError();
|
|
|
|
|
}
|
|
|
|
|
auto transceiver = transceiver_or_error.MoveValue();
|
|
|
|
|
RTCError error =
|
|
|
|
|
UpdateTransceiverChannel(transceiver, new_content, bundle_group);
|
[Rollback] Don't end tracks when transceiver is still in use.
Prior to this CL, calling RtpTransceiver::SetChannel() with null
arguments would cause the receiver's track to end. This is wrong,
because the channel can be nulled for other reasons than the transceiver
being stopped/removed - such as when the transceiver is rolled back but
still in use. Also, stopping a transceiver will end the track, so we
should simply ensure to always stop the transceiver when that is needed.
This CL makes sure that the transceiver is stopped or stopping in all
appropriate places, allowing us to remove the ability to end the source
for any other reason. A side-effect of this is that:
- The track never ends prematurely, fixing https://crbug.com/1315611.
- Removed transceivers are always stopped, fixing
https://crbug.com/webrtc/14005.
This CL fixes the issue of track being ended in the ontrack event when
running https://jsfiddle.net/henbos/nxebusjm/.
- We don't have WPT test coverage for this, so I'll add that separately.
With SetSourceEnded() removed, some stopping/stop in response to
rejecting locally SDP munged content had to be added in order not to
regress the existing test coverage for this:
*PeerConnectionInterfaceTest.RejectMediaContent/1
Bug: chromium:1315611, webrtc:14005.
Change-Id: I21f30a1259e51324066dc84f72a72485b9e0fadc
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/260180
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Henrik Boström <hbos@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#36669}
2022-04-27 11:54:01 +02:00
|
|
|
// Handle locally rejected content. This code path is only needed for apps
|
|
|
|
|
// that SDP munge. Remote rejected content is handled in
|
|
|
|
|
// ApplyRemoteDescriptionUpdateTransceiverState().
|
|
|
|
|
if (source == cricket::ContentSource::CS_LOCAL && new_content.rejected) {
|
|
|
|
|
// Local offer.
|
|
|
|
|
if (new_session.GetType() == SdpType::kOffer) {
|
|
|
|
|
// If the RtpTransceiver API was used, it would already have made the
|
|
|
|
|
// transceiver stopping. But if the rejection was caused by SDP
|
|
|
|
|
// munging then we need to ensure the transceiver is stopping here.
|
|
|
|
|
if (!transceiver->internal()->stopping()) {
|
|
|
|
|
transceiver->internal()->StopStandard();
|
|
|
|
|
}
|
|
|
|
|
RTC_DCHECK(transceiver->internal()->stopping());
|
|
|
|
|
} else {
|
|
|
|
|
// Local answer.
|
|
|
|
|
RTC_DCHECK(new_session.GetType() == SdpType::kAnswer ||
|
|
|
|
|
new_session.GetType() == SdpType::kPrAnswer);
|
|
|
|
|
// When RtpTransceiver API is used, rejection happens in the offer and
|
|
|
|
|
// the transceiver will already be stopped at local answer time
|
|
|
|
|
// (calling stop between SRD(offer) and SLD(answer) would not reject
|
|
|
|
|
// the content in the answer - instead this would trigger a follow-up
|
|
|
|
|
// O/A exchange). So if the content was rejected but the transceiver
|
|
|
|
|
// is not already stopped, SDP munging has happened and we need to
|
|
|
|
|
// ensure the transceiver is stopped.
|
|
|
|
|
if (!transceiver->internal()->stopped()) {
|
|
|
|
|
transceiver->internal()->StopTransceiverProcedure();
|
|
|
|
|
}
|
|
|
|
|
RTC_DCHECK(transceiver->internal()->stopped());
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-09-28 13:02:07 +00:00
|
|
|
if (!error.ok()) {
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
} else if (media_type == cricket::MEDIA_TYPE_DATA) {
|
2023-10-21 16:47:56 +02:00
|
|
|
const auto data_mid = pc_->sctp_mid();
|
|
|
|
|
if (data_mid && new_content.name != data_mid.value()) {
|
2020-09-28 13:02:07 +00:00
|
|
|
// Ignore all but the first data section.
|
|
|
|
|
RTC_LOG(LS_INFO) << "Ignoring data media section with MID="
|
|
|
|
|
<< new_content.name;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2023-10-21 16:47:56 +02:00
|
|
|
RTCError error =
|
|
|
|
|
UpdateDataChannelTransport(source, new_content, bundle_group);
|
2020-09-28 13:02:07 +00:00
|
|
|
if (!error.ok()) {
|
|
|
|
|
return error;
|
|
|
|
|
}
|
2020-10-13 12:43:15 +02:00
|
|
|
} else if (media_type == cricket::MEDIA_TYPE_UNSUPPORTED) {
|
|
|
|
|
RTC_LOG(LS_INFO) << "Ignoring unsupported media type";
|
2020-09-28 13:02:07 +00:00
|
|
|
} else {
|
2023-07-18 11:03:39 +02:00
|
|
|
LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR,
|
|
|
|
|
"Unknown section type.");
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return RTCError::OK();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RTCErrorOr<rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>>
|
|
|
|
|
SdpOfferAnswerHandler::AssociateTransceiver(
|
|
|
|
|
cricket::ContentSource source,
|
|
|
|
|
SdpType type,
|
|
|
|
|
size_t mline_index,
|
|
|
|
|
const ContentInfo& content,
|
|
|
|
|
const ContentInfo* old_local_content,
|
|
|
|
|
const ContentInfo* old_remote_content) {
|
2021-06-07 13:30:46 +02:00
|
|
|
TRACE_EVENT0("webrtc", "SdpOfferAnswerHandler::AssociateTransceiver");
|
2020-09-28 13:02:07 +00:00
|
|
|
RTC_DCHECK(IsUnifiedPlan());
|
2020-10-05 21:41:36 +02:00
|
|
|
#if RTC_DCHECK_IS_ON
|
2020-09-28 13:02:07 +00:00
|
|
|
// If this is an offer then the m= section might be recycled. If the m=
|
|
|
|
|
// section is being recycled (defined as: rejected in the current local or
|
|
|
|
|
// remote description and not rejected in new description), the transceiver
|
2020-10-19 13:28:05 +00:00
|
|
|
// should have been removed by RemoveStoppedtransceivers()->
|
2020-09-28 13:02:07 +00:00
|
|
|
if (IsMediaSectionBeingRecycled(type, content, old_local_content,
|
|
|
|
|
old_remote_content)) {
|
|
|
|
|
const std::string& old_mid =
|
|
|
|
|
(old_local_content && old_local_content->rejected)
|
|
|
|
|
? old_local_content->name
|
|
|
|
|
: old_remote_content->name;
|
2020-10-19 13:28:05 +00:00
|
|
|
auto old_transceiver = transceivers()->FindByMid(old_mid);
|
2020-09-28 13:02:07 +00:00
|
|
|
// The transceiver should be disassociated in RemoveStoppedTransceivers()
|
|
|
|
|
RTC_DCHECK(!old_transceiver);
|
|
|
|
|
}
|
2020-10-05 21:41:36 +02:00
|
|
|
#endif
|
|
|
|
|
|
2020-09-28 13:02:07 +00:00
|
|
|
const MediaContentDescription* media_desc = content.media_description();
|
2020-10-19 13:28:05 +00:00
|
|
|
auto transceiver = transceivers()->FindByMid(content.name);
|
2020-09-28 13:02:07 +00:00
|
|
|
if (source == cricket::CS_LOCAL) {
|
|
|
|
|
// Find the RtpTransceiver that corresponds to this m= section, using the
|
|
|
|
|
// mapping between transceivers and m= section indices established when
|
|
|
|
|
// creating the offer.
|
|
|
|
|
if (!transceiver) {
|
2020-10-19 13:28:05 +00:00
|
|
|
transceiver = transceivers()->FindByMLineIndex(mline_index);
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
if (!transceiver) {
|
2020-10-09 08:13:30 +00:00
|
|
|
// This may happen normally when media sections are rejected.
|
2023-07-18 11:03:39 +02:00
|
|
|
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
|
|
|
|
|
"Transceiver not found based on m-line index");
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
RTC_DCHECK_EQ(source, cricket::CS_REMOTE);
|
|
|
|
|
// If the m= section is sendrecv or recvonly, and there are RtpTransceivers
|
|
|
|
|
// of the same type...
|
|
|
|
|
// When simulcast is requested, a transceiver cannot be associated because
|
|
|
|
|
// AddTrack cannot be called to initialize it.
|
|
|
|
|
if (!transceiver &&
|
|
|
|
|
RtpTransceiverDirectionHasRecv(media_desc->direction()) &&
|
|
|
|
|
!media_desc->HasSimulcast()) {
|
2020-10-01 10:23:33 +00:00
|
|
|
transceiver = FindAvailableTransceiverToReceive(media_desc->type());
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
// If no RtpTransceiver was found in the previous step, create one with a
|
|
|
|
|
// recvonly direction.
|
|
|
|
|
if (!transceiver) {
|
|
|
|
|
RTC_LOG(LS_INFO) << "Adding "
|
|
|
|
|
<< cricket::MediaTypeToString(media_desc->type())
|
|
|
|
|
<< " transceiver for MID=" << content.name
|
|
|
|
|
<< " at i=" << mline_index
|
|
|
|
|
<< " in response to the remote description.";
|
|
|
|
|
std::string sender_id = rtc::CreateRandomUuid();
|
|
|
|
|
std::vector<RtpEncodingParameters> send_encodings =
|
|
|
|
|
GetSendEncodingsFromRemoteDescription(*media_desc);
|
2020-10-19 13:28:05 +00:00
|
|
|
auto sender = rtp_manager()->CreateSender(media_desc->type(), sender_id,
|
|
|
|
|
nullptr, {}, send_encodings);
|
2020-09-28 13:02:07 +00:00
|
|
|
std::string receiver_id;
|
|
|
|
|
if (!media_desc->streams().empty()) {
|
|
|
|
|
receiver_id = media_desc->streams()[0].id;
|
|
|
|
|
} else {
|
|
|
|
|
receiver_id = rtc::CreateRandomUuid();
|
|
|
|
|
}
|
2020-10-19 13:28:05 +00:00
|
|
|
auto receiver =
|
|
|
|
|
rtp_manager()->CreateReceiver(media_desc->type(), receiver_id);
|
|
|
|
|
transceiver = rtp_manager()->CreateAndAddTransceiver(sender, receiver);
|
2020-09-28 13:02:07 +00:00
|
|
|
transceiver->internal()->set_direction(
|
|
|
|
|
RtpTransceiverDirection::kRecvOnly);
|
|
|
|
|
if (type == SdpType::kOffer) {
|
2020-10-19 13:28:05 +00:00
|
|
|
transceivers()->StableState(transceiver)->set_newly_created();
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
}
|
2020-10-05 21:41:36 +02:00
|
|
|
|
|
|
|
|
RTC_DCHECK(transceiver);
|
|
|
|
|
|
2020-09-28 13:02:07 +00:00
|
|
|
// Check if the offer indicated simulcast but the answer rejected it.
|
|
|
|
|
// This can happen when simulcast is not supported on the remote party.
|
2021-05-17 16:06:37 +02:00
|
|
|
if (SimulcastIsRejected(old_local_content, *media_desc,
|
|
|
|
|
pc_->GetCryptoOptions()
|
|
|
|
|
.srtp.enable_encrypted_rtp_header_extensions)) {
|
2020-09-28 13:02:07 +00:00
|
|
|
RTCError error =
|
|
|
|
|
DisableSimulcastInSender(transceiver->internal()->sender_internal());
|
|
|
|
|
if (!error.ok()) {
|
|
|
|
|
RTC_LOG(LS_ERROR) << "Failed to remove rejected simulcast.";
|
|
|
|
|
return std::move(error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-05 21:41:36 +02:00
|
|
|
|
2020-09-28 13:02:07 +00:00
|
|
|
if (transceiver->media_type() != media_desc->type()) {
|
2023-07-18 11:03:39 +02:00
|
|
|
LOG_AND_RETURN_ERROR(
|
|
|
|
|
RTCErrorType::INVALID_PARAMETER,
|
|
|
|
|
"Transceiver type does not match media description type.");
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
2020-10-05 21:41:36 +02:00
|
|
|
|
2020-09-28 13:02:07 +00:00
|
|
|
if (media_desc->HasSimulcast()) {
|
|
|
|
|
std::vector<SimulcastLayer> layers =
|
|
|
|
|
source == cricket::CS_LOCAL
|
|
|
|
|
? media_desc->simulcast_description().send_layers().GetAllLayers()
|
|
|
|
|
: media_desc->simulcast_description()
|
|
|
|
|
.receive_layers()
|
|
|
|
|
.GetAllLayers();
|
|
|
|
|
RTCError error = UpdateSimulcastLayerStatusInSender(
|
|
|
|
|
layers, transceiver->internal()->sender_internal());
|
|
|
|
|
if (!error.ok()) {
|
|
|
|
|
RTC_LOG(LS_ERROR) << "Failed updating status for simulcast layers.";
|
|
|
|
|
return std::move(error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (type == SdpType::kOffer) {
|
|
|
|
|
bool state_changes = transceiver->internal()->mid() != content.name ||
|
|
|
|
|
transceiver->internal()->mline_index() != mline_index;
|
|
|
|
|
if (state_changes) {
|
2020-10-05 13:08:41 +00:00
|
|
|
transceivers()
|
2020-10-19 13:28:05 +00:00
|
|
|
->StableState(transceiver)
|
2020-09-29 11:54:05 +00:00
|
|
|
->SetMSectionIfUnset(transceiver->internal()->mid(),
|
|
|
|
|
transceiver->internal()->mline_index());
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Associate the found or created RtpTransceiver with the m= section by
|
|
|
|
|
// setting the value of the RtpTransceiver's mid property to the MID of the m=
|
|
|
|
|
// section, and establish a mapping between the transceiver and the index of
|
|
|
|
|
// the m= section.
|
|
|
|
|
transceiver->internal()->set_mid(content.name);
|
|
|
|
|
transceiver->internal()->set_mline_index(mline_index);
|
|
|
|
|
return std::move(transceiver);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RTCError SdpOfferAnswerHandler::UpdateTransceiverChannel(
|
|
|
|
|
rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
|
|
|
|
|
transceiver,
|
|
|
|
|
const cricket::ContentInfo& content,
|
|
|
|
|
const cricket::ContentGroup* bundle_group) {
|
2021-06-07 13:30:46 +02:00
|
|
|
TRACE_EVENT0("webrtc", "SdpOfferAnswerHandler::UpdateTransceiverChannel");
|
2020-09-28 13:02:07 +00:00
|
|
|
RTC_DCHECK(IsUnifiedPlan());
|
|
|
|
|
RTC_DCHECK(transceiver);
|
|
|
|
|
cricket::ChannelInterface* channel = transceiver->internal()->channel();
|
|
|
|
|
if (content.rejected) {
|
|
|
|
|
if (channel) {
|
2022-04-28 13:31:17 +00:00
|
|
|
transceiver->internal()->ClearChannel();
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (!channel) {
|
2022-05-04 10:32:30 +00:00
|
|
|
auto error = transceiver->internal()->CreateChannel(
|
|
|
|
|
content.name, pc_->call_ptr(), pc_->configuration()->media_config,
|
|
|
|
|
pc_->SrtpRequired(), pc_->GetCryptoOptions(), audio_options(),
|
|
|
|
|
video_options(), video_bitrate_allocator_factory_.get(),
|
|
|
|
|
[&](absl::string_view mid) {
|
2022-04-29 15:04:58 +00:00
|
|
|
RTC_DCHECK_RUN_ON(network_thread());
|
|
|
|
|
return transport_controller_n()->GetRtpTransport(mid);
|
|
|
|
|
});
|
2022-05-04 10:32:30 +00:00
|
|
|
if (!error.ok()) {
|
|
|
|
|
return error;
|
|
|
|
|
}
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return RTCError::OK();
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-21 16:47:56 +02:00
|
|
|
RTCError SdpOfferAnswerHandler::UpdateDataChannelTransport(
|
2020-09-28 13:02:07 +00:00
|
|
|
cricket::ContentSource source,
|
|
|
|
|
const cricket::ContentInfo& content,
|
|
|
|
|
const cricket::ContentGroup* bundle_group) {
|
|
|
|
|
if (content.rejected) {
|
2021-06-29 14:58:23 +02:00
|
|
|
RTC_LOG(LS_INFO) << "Rejected data channel transport with mid="
|
|
|
|
|
<< content.mid();
|
|
|
|
|
|
|
|
|
|
rtc::StringBuilder sb;
|
|
|
|
|
sb << "Rejected data channel transport with mid=" << content.mid();
|
|
|
|
|
RTCError error(RTCErrorType::OPERATION_ERROR_WITH_DATA, sb.Release());
|
|
|
|
|
error.set_error_detail(RTCErrorDetailType::DATA_CHANNEL_FAILURE);
|
2023-10-21 16:47:56 +02:00
|
|
|
pc_->DestroyDataChannelTransport(error);
|
|
|
|
|
} else if (!pc_->CreateDataChannelTransport(content.name)) {
|
2023-07-18 11:03:39 +02:00
|
|
|
LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR,
|
|
|
|
|
"Failed to create data channel.");
|
2020-09-28 13:02:07 +00:00
|
|
|
}
|
|
|
|
|
return RTCError::OK();
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-01 10:23:33 +00:00
|
|
|
bool SdpOfferAnswerHandler::ExpectSetLocalDescription(SdpType type) {
|
|
|
|
|
PeerConnectionInterface::SignalingState state = signaling_state();
|
|
|
|
|
if (type == SdpType::kOffer) {
|
|
|
|
|
return (state == PeerConnectionInterface::kStable) ||
|
|
|
|
|
(state == PeerConnectionInterface::kHaveLocalOffer);
|
|
|
|
|
} else {
|
|
|
|
|
RTC_DCHECK(type == SdpType::kPrAnswer || type == SdpType::kAnswer);
|
|
|
|
|
return (state == PeerConnectionInterface::kHaveRemoteOffer) ||
|
|
|
|
|
(state == PeerConnectionInterface::kHaveLocalPrAnswer);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool SdpOfferAnswerHandler::ExpectSetRemoteDescription(SdpType type) {
|
|
|
|
|
PeerConnectionInterface::SignalingState state = signaling_state();
|
|
|
|
|
if (type == SdpType::kOffer) {
|
|
|
|
|
return (state == PeerConnectionInterface::kStable) ||
|
|
|
|
|
(state == PeerConnectionInterface::kHaveRemoteOffer);
|
|
|
|
|
} else {
|
|
|
|
|
RTC_DCHECK(type == SdpType::kPrAnswer || type == SdpType::kAnswer);
|
|
|
|
|
return (state == PeerConnectionInterface::kHaveLocalOffer) ||
|
|
|
|
|
(state == PeerConnectionInterface::kHaveRemotePrAnswer);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::FillInMissingRemoteMids(
|
|
|
|
|
cricket::SessionDescription* new_remote_description) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
RTC_DCHECK(new_remote_description);
|
|
|
|
|
const cricket::ContentInfos no_infos;
|
|
|
|
|
const cricket::ContentInfos& local_contents =
|
|
|
|
|
(local_description() ? local_description()->description()->contents()
|
|
|
|
|
: no_infos);
|
|
|
|
|
const cricket::ContentInfos& remote_contents =
|
|
|
|
|
(remote_description() ? remote_description()->description()->contents()
|
|
|
|
|
: no_infos);
|
|
|
|
|
for (size_t i = 0; i < new_remote_description->contents().size(); ++i) {
|
|
|
|
|
cricket::ContentInfo& content = new_remote_description->contents()[i];
|
|
|
|
|
if (!content.name.empty()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
std::string new_mid;
|
|
|
|
|
absl::string_view source_explanation;
|
|
|
|
|
if (IsUnifiedPlan()) {
|
|
|
|
|
if (i < local_contents.size()) {
|
|
|
|
|
new_mid = local_contents[i].name;
|
|
|
|
|
source_explanation = "from the matching local media section";
|
|
|
|
|
} else if (i < remote_contents.size()) {
|
|
|
|
|
new_mid = remote_contents[i].name;
|
|
|
|
|
source_explanation = "from the matching previous remote media section";
|
|
|
|
|
} else {
|
2020-10-05 13:08:41 +00:00
|
|
|
new_mid = mid_generator_.GenerateString();
|
2020-10-01 10:23:33 +00:00
|
|
|
source_explanation = "generated just now";
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
new_mid = std::string(
|
|
|
|
|
GetDefaultMidForPlanB(content.media_description()->type()));
|
|
|
|
|
source_explanation = "to match pre-existing behavior";
|
|
|
|
|
}
|
|
|
|
|
RTC_DCHECK(!new_mid.empty());
|
|
|
|
|
content.name = new_mid;
|
|
|
|
|
new_remote_description->transport_infos()[i].content_name = new_mid;
|
|
|
|
|
RTC_LOG(LS_INFO) << "SetRemoteDescription: Remote media section at i=" << i
|
|
|
|
|
<< " is missing an a=mid line. Filling in the value '"
|
|
|
|
|
<< new_mid << "' " << source_explanation << ".";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
|
|
|
|
|
SdpOfferAnswerHandler::FindAvailableTransceiverToReceive(
|
|
|
|
|
cricket::MediaType media_type) const {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
RTC_DCHECK(IsUnifiedPlan());
|
|
|
|
|
// From JSEP section 5.10 (Applying a Remote Description):
|
|
|
|
|
// If the m= section is sendrecv or recvonly, and there are RtpTransceivers of
|
|
|
|
|
// the same type that were added to the PeerConnection by addTrack and are not
|
|
|
|
|
// associated with any m= section and are not stopped, find the first such
|
|
|
|
|
// RtpTransceiver.
|
2020-10-19 13:28:05 +00:00
|
|
|
for (auto transceiver : transceivers()->List()) {
|
2020-10-01 10:23:33 +00:00
|
|
|
if (transceiver->media_type() == media_type &&
|
|
|
|
|
transceiver->internal()->created_by_addtrack() && !transceiver->mid() &&
|
|
|
|
|
!transceiver->stopped()) {
|
|
|
|
|
return transceiver;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const cricket::ContentInfo*
|
|
|
|
|
SdpOfferAnswerHandler::FindMediaSectionForTransceiver(
|
2021-04-19 21:21:36 +00:00
|
|
|
const RtpTransceiver* transceiver,
|
2020-10-01 10:23:33 +00:00
|
|
|
const SessionDescriptionInterface* sdesc) const {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
RTC_DCHECK(transceiver);
|
|
|
|
|
RTC_DCHECK(sdesc);
|
|
|
|
|
if (IsUnifiedPlan()) {
|
2021-04-19 21:21:36 +00:00
|
|
|
if (!transceiver->mid()) {
|
2020-10-01 10:23:33 +00:00
|
|
|
// This transceiver is not associated with a media section yet.
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
2021-04-19 21:21:36 +00:00
|
|
|
return sdesc->description()->GetContentByName(*transceiver->mid());
|
2020-10-01 10:23:33 +00:00
|
|
|
} else {
|
|
|
|
|
// Plan B only allows at most one audio and one video section, so use the
|
|
|
|
|
// first media section of that type.
|
|
|
|
|
return cricket::GetFirstMediaContent(sdesc->description()->contents(),
|
|
|
|
|
transceiver->media_type());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::GetOptionsForOffer(
|
|
|
|
|
const PeerConnectionInterface::RTCOfferAnswerOptions& offer_answer_options,
|
|
|
|
|
cricket::MediaSessionOptions* session_options) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
ExtractSharedMediaSessionOptions(offer_answer_options, session_options);
|
|
|
|
|
|
|
|
|
|
if (IsUnifiedPlan()) {
|
|
|
|
|
GetOptionsForUnifiedPlanOffer(offer_answer_options, session_options);
|
|
|
|
|
} else {
|
|
|
|
|
GetOptionsForPlanBOffer(offer_answer_options, session_options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Apply ICE restart flag and renomination flag.
|
|
|
|
|
bool ice_restart = offer_answer_options.ice_restart || HasNewIceCredentials();
|
|
|
|
|
for (auto& options : session_options->media_description_options) {
|
|
|
|
|
options.transport_options.ice_restart = ice_restart;
|
|
|
|
|
options.transport_options.enable_ice_renomination =
|
|
|
|
|
pc_->configuration()->enable_ice_renomination;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-05 13:08:41 +00:00
|
|
|
session_options->rtcp_cname = rtcp_cname_;
|
2020-10-01 10:23:33 +00:00
|
|
|
session_options->crypto_options = pc_->GetCryptoOptions();
|
|
|
|
|
session_options->pooled_ice_credentials =
|
2022-09-08 18:38:10 +02:00
|
|
|
context_->network_thread()->BlockingCall(
|
2021-01-18 09:24:33 +01:00
|
|
|
[this] { return port_allocator()->GetPooledIceCredentials(); });
|
2020-10-01 10:23:33 +00:00
|
|
|
session_options->offer_extmap_allow_mixed =
|
|
|
|
|
pc_->configuration()->offer_extmap_allow_mixed;
|
|
|
|
|
|
|
|
|
|
// Allow fallback for using obsolete SCTP syntax.
|
2021-07-30 22:30:23 +02:00
|
|
|
// Note that the default in `session_options` is true, while
|
|
|
|
|
// the default in `options` is false.
|
2020-10-01 10:23:33 +00:00
|
|
|
session_options->use_obsolete_sctp_sdp =
|
|
|
|
|
offer_answer_options.use_obsolete_sctp_sdp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::GetOptionsForPlanBOffer(
|
|
|
|
|
const PeerConnectionInterface::RTCOfferAnswerOptions& offer_answer_options,
|
|
|
|
|
cricket::MediaSessionOptions* session_options) {
|
2022-05-09 08:30:09 +00:00
|
|
|
bool offer_new_data_description =
|
2023-03-15 20:39:42 +00:00
|
|
|
data_channel_controller()->HasUsedDataChannels();
|
2022-05-23 14:57:47 +00:00
|
|
|
bool send_audio = false;
|
|
|
|
|
bool send_video = false;
|
|
|
|
|
bool recv_audio = false;
|
|
|
|
|
bool recv_video = false;
|
|
|
|
|
if (ConfiguredForMedia()) {
|
|
|
|
|
// Figure out transceiver directional preferences.
|
|
|
|
|
send_audio =
|
|
|
|
|
!rtp_manager()->GetAudioTransceiver()->internal()->senders().empty();
|
|
|
|
|
send_video =
|
|
|
|
|
!rtp_manager()->GetVideoTransceiver()->internal()->senders().empty();
|
|
|
|
|
|
|
|
|
|
// By default, generate sendrecv/recvonly m= sections.
|
|
|
|
|
recv_audio = true;
|
|
|
|
|
recv_video = true;
|
2022-05-09 08:30:09 +00:00
|
|
|
}
|
2022-05-23 14:57:47 +00:00
|
|
|
// By default, only offer a new m= section if we have media to send with it.
|
|
|
|
|
bool offer_new_audio_description = send_audio;
|
|
|
|
|
bool offer_new_video_description = send_video;
|
|
|
|
|
if (ConfiguredForMedia()) {
|
|
|
|
|
// The "offer_to_receive_X" options allow those defaults to be overridden.
|
|
|
|
|
if (offer_answer_options.offer_to_receive_audio !=
|
|
|
|
|
PeerConnectionInterface::RTCOfferAnswerOptions::kUndefined) {
|
|
|
|
|
recv_audio = (offer_answer_options.offer_to_receive_audio > 0);
|
|
|
|
|
offer_new_audio_description =
|
|
|
|
|
offer_new_audio_description ||
|
|
|
|
|
(offer_answer_options.offer_to_receive_audio > 0);
|
|
|
|
|
}
|
|
|
|
|
if (offer_answer_options.offer_to_receive_video !=
|
|
|
|
|
RTCOfferAnswerOptions::kUndefined) {
|
|
|
|
|
recv_video = (offer_answer_options.offer_to_receive_video > 0);
|
|
|
|
|
offer_new_video_description =
|
|
|
|
|
offer_new_video_description ||
|
|
|
|
|
(offer_answer_options.offer_to_receive_video > 0);
|
|
|
|
|
}
|
2020-10-01 10:23:33 +00:00
|
|
|
}
|
2024-08-29 13:00:40 +00:00
|
|
|
std::optional<size_t> audio_index;
|
|
|
|
|
std::optional<size_t> video_index;
|
|
|
|
|
std::optional<size_t> data_index;
|
2020-10-01 10:23:33 +00:00
|
|
|
// If a current description exists, generate m= sections in the same order,
|
|
|
|
|
// using the first audio/video/data section that appears and rejecting
|
|
|
|
|
// extraneous ones.
|
|
|
|
|
if (local_description()) {
|
2020-10-05 13:08:41 +00:00
|
|
|
GenerateMediaDescriptionOptions(
|
2020-10-01 10:23:33 +00:00
|
|
|
local_description(),
|
|
|
|
|
RtpTransceiverDirectionFromSendRecv(send_audio, recv_audio),
|
|
|
|
|
RtpTransceiverDirectionFromSendRecv(send_video, recv_video),
|
|
|
|
|
&audio_index, &video_index, &data_index, session_options);
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-23 14:57:47 +00:00
|
|
|
if (ConfiguredForMedia()) {
|
|
|
|
|
// Add audio/video/data m= sections to the end if needed.
|
|
|
|
|
if (!audio_index && offer_new_audio_description) {
|
|
|
|
|
cricket::MediaDescriptionOptions options(
|
|
|
|
|
cricket::MEDIA_TYPE_AUDIO, cricket::CN_AUDIO,
|
|
|
|
|
RtpTransceiverDirectionFromSendRecv(send_audio, recv_audio), false);
|
|
|
|
|
options.header_extensions =
|
|
|
|
|
media_engine()->voice().GetRtpHeaderExtensions();
|
|
|
|
|
session_options->media_description_options.push_back(options);
|
|
|
|
|
audio_index = session_options->media_description_options.size() - 1;
|
|
|
|
|
}
|
|
|
|
|
if (!video_index && offer_new_video_description) {
|
|
|
|
|
cricket::MediaDescriptionOptions options(
|
|
|
|
|
cricket::MEDIA_TYPE_VIDEO, cricket::CN_VIDEO,
|
|
|
|
|
RtpTransceiverDirectionFromSendRecv(send_video, recv_video), false);
|
|
|
|
|
options.header_extensions =
|
|
|
|
|
media_engine()->video().GetRtpHeaderExtensions();
|
|
|
|
|
session_options->media_description_options.push_back(options);
|
|
|
|
|
video_index = session_options->media_description_options.size() - 1;
|
|
|
|
|
}
|
|
|
|
|
cricket::MediaDescriptionOptions* audio_media_description_options =
|
|
|
|
|
!audio_index
|
|
|
|
|
? nullptr
|
|
|
|
|
: &session_options->media_description_options[*audio_index];
|
|
|
|
|
cricket::MediaDescriptionOptions* video_media_description_options =
|
|
|
|
|
!video_index
|
|
|
|
|
? nullptr
|
|
|
|
|
: &session_options->media_description_options[*video_index];
|
|
|
|
|
|
|
|
|
|
AddPlanBRtpSenderOptions(rtp_manager()->GetSendersInternal(),
|
|
|
|
|
audio_media_description_options,
|
|
|
|
|
video_media_description_options,
|
|
|
|
|
offer_answer_options.num_simulcast_layers);
|
2020-10-01 10:23:33 +00:00
|
|
|
}
|
|
|
|
|
if (!data_index && offer_new_data_description) {
|
|
|
|
|
session_options->media_description_options.push_back(
|
2020-10-05 13:08:41 +00:00
|
|
|
GetMediaDescriptionOptionsForActiveData(cricket::CN_DATA));
|
2020-10-01 10:23:33 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::GetOptionsForUnifiedPlanOffer(
|
|
|
|
|
const RTCOfferAnswerOptions& offer_answer_options,
|
|
|
|
|
cricket::MediaSessionOptions* session_options) {
|
|
|
|
|
// Rules for generating an offer are dictated by JSEP sections 5.2.1 (Initial
|
|
|
|
|
// Offers) and 5.2.2 (Subsequent Offers).
|
|
|
|
|
RTC_DCHECK_EQ(session_options->media_description_options.size(), 0);
|
|
|
|
|
const ContentInfos no_infos;
|
|
|
|
|
const ContentInfos& local_contents =
|
|
|
|
|
(local_description() ? local_description()->description()->contents()
|
|
|
|
|
: no_infos);
|
|
|
|
|
const ContentInfos& remote_contents =
|
|
|
|
|
(remote_description() ? remote_description()->description()->contents()
|
|
|
|
|
: no_infos);
|
|
|
|
|
// The mline indices that can be recycled. New transceivers should reuse these
|
|
|
|
|
// slots first.
|
|
|
|
|
std::queue<size_t> recycleable_mline_indices;
|
|
|
|
|
// First, go through each media section that exists in either the local or
|
|
|
|
|
// remote description and generate a media section in this offer for the
|
|
|
|
|
// associated transceiver. If a media section can be recycled, generate a
|
|
|
|
|
// default, rejected media section here that can be later overwritten.
|
|
|
|
|
for (size_t i = 0;
|
|
|
|
|
i < std::max(local_contents.size(), remote_contents.size()); ++i) {
|
2021-07-30 22:30:23 +02:00
|
|
|
// Either `local_content` or `remote_content` is non-null.
|
2020-10-01 10:23:33 +00:00
|
|
|
const ContentInfo* local_content =
|
|
|
|
|
(i < local_contents.size() ? &local_contents[i] : nullptr);
|
|
|
|
|
const ContentInfo* current_local_content =
|
|
|
|
|
GetContentByIndex(current_local_description(), i);
|
|
|
|
|
const ContentInfo* remote_content =
|
|
|
|
|
(i < remote_contents.size() ? &remote_contents[i] : nullptr);
|
|
|
|
|
const ContentInfo* current_remote_content =
|
|
|
|
|
GetContentByIndex(current_remote_description(), i);
|
|
|
|
|
bool had_been_rejected =
|
|
|
|
|
(current_local_content && current_local_content->rejected) ||
|
|
|
|
|
(current_remote_content && current_remote_content->rejected);
|
|
|
|
|
const std::string& mid =
|
|
|
|
|
(local_content ? local_content->name : remote_content->name);
|
|
|
|
|
cricket::MediaType media_type =
|
|
|
|
|
(local_content ? local_content->media_description()->type()
|
|
|
|
|
: remote_content->media_description()->type());
|
|
|
|
|
if (media_type == cricket::MEDIA_TYPE_AUDIO ||
|
|
|
|
|
media_type == cricket::MEDIA_TYPE_VIDEO) {
|
|
|
|
|
// A media section is considered eligible for recycling if it is marked as
|
|
|
|
|
// rejected in either the current local or current remote description.
|
2020-10-19 13:28:05 +00:00
|
|
|
auto transceiver = transceivers()->FindByMid(mid);
|
2020-10-01 10:23:33 +00:00
|
|
|
if (!transceiver) {
|
|
|
|
|
// No associated transceiver. The media section has been stopped.
|
|
|
|
|
recycleable_mline_indices.push(i);
|
|
|
|
|
session_options->media_description_options.push_back(
|
|
|
|
|
cricket::MediaDescriptionOptions(media_type, mid,
|
|
|
|
|
RtpTransceiverDirection::kInactive,
|
|
|
|
|
/*stopped=*/true));
|
|
|
|
|
} else {
|
|
|
|
|
// NOTE: a stopping transceiver should be treated as a stopped one in
|
|
|
|
|
// createOffer as specified in
|
|
|
|
|
// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-createoffer.
|
|
|
|
|
if (had_been_rejected && transceiver->stopping()) {
|
|
|
|
|
session_options->media_description_options.push_back(
|
|
|
|
|
cricket::MediaDescriptionOptions(
|
|
|
|
|
transceiver->media_type(), mid,
|
|
|
|
|
RtpTransceiverDirection::kInactive,
|
|
|
|
|
/*stopped=*/true));
|
|
|
|
|
recycleable_mline_indices.push(i);
|
|
|
|
|
} else {
|
|
|
|
|
session_options->media_description_options.push_back(
|
|
|
|
|
GetMediaDescriptionOptionsForTransceiver(
|
2021-04-19 21:21:36 +00:00
|
|
|
transceiver->internal(), mid,
|
2020-10-01 10:23:33 +00:00
|
|
|
/*is_create_offer=*/true));
|
|
|
|
|
// CreateOffer shouldn't really cause any state changes in
|
|
|
|
|
// PeerConnection, but we need a way to match new transceivers to new
|
|
|
|
|
// media sections in SetLocalDescription and JSEP specifies this is
|
|
|
|
|
// done by recording the index of the media section generated for the
|
|
|
|
|
// transceiver in the offer.
|
|
|
|
|
transceiver->internal()->set_mline_index(i);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-13 12:43:15 +02:00
|
|
|
} else if (media_type == cricket::MEDIA_TYPE_UNSUPPORTED) {
|
|
|
|
|
RTC_DCHECK(local_content->rejected);
|
|
|
|
|
session_options->media_description_options.push_back(
|
|
|
|
|
cricket::MediaDescriptionOptions(media_type, mid,
|
|
|
|
|
RtpTransceiverDirection::kInactive,
|
|
|
|
|
/*stopped=*/true));
|
2020-10-01 10:23:33 +00:00
|
|
|
} else {
|
|
|
|
|
RTC_CHECK_EQ(cricket::MEDIA_TYPE_DATA, media_type);
|
|
|
|
|
if (had_been_rejected) {
|
|
|
|
|
session_options->media_description_options.push_back(
|
2020-10-05 13:08:41 +00:00
|
|
|
GetMediaDescriptionOptionsForRejectedData(mid));
|
2020-10-01 10:23:33 +00:00
|
|
|
} else {
|
2023-10-21 16:47:56 +02:00
|
|
|
const auto data_mid = pc_->sctp_mid();
|
2024-02-23 14:49:05 +01:00
|
|
|
if (data_mid.has_value() && mid == data_mid.value()) {
|
2020-10-01 10:23:33 +00:00
|
|
|
session_options->media_description_options.push_back(
|
2020-10-05 13:08:41 +00:00
|
|
|
GetMediaDescriptionOptionsForActiveData(mid));
|
2020-10-01 10:23:33 +00:00
|
|
|
} else {
|
2024-02-23 14:49:05 +01:00
|
|
|
if (!data_mid.has_value()) {
|
|
|
|
|
RTC_LOG(LS_ERROR) << "Datachannel transport not available: " << mid;
|
|
|
|
|
}
|
2020-10-01 10:23:33 +00:00
|
|
|
session_options->media_description_options.push_back(
|
2020-10-05 13:08:41 +00:00
|
|
|
GetMediaDescriptionOptionsForRejectedData(mid));
|
2020-10-01 10:23:33 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Next, look for transceivers that are newly added (that is, are not stopped
|
|
|
|
|
// and not associated). Reuse media sections marked as recyclable first,
|
|
|
|
|
// otherwise append to the end of the offer. New media sections should be
|
|
|
|
|
// added in the order they were added to the PeerConnection.
|
2022-05-23 14:57:47 +00:00
|
|
|
if (ConfiguredForMedia()) {
|
|
|
|
|
for (const auto& transceiver : transceivers()->ListInternal()) {
|
|
|
|
|
if (transceiver->mid() || transceiver->stopping()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
size_t mline_index;
|
|
|
|
|
if (!recycleable_mline_indices.empty()) {
|
|
|
|
|
mline_index = recycleable_mline_indices.front();
|
|
|
|
|
recycleable_mline_indices.pop();
|
|
|
|
|
session_options->media_description_options[mline_index] =
|
|
|
|
|
GetMediaDescriptionOptionsForTransceiver(
|
|
|
|
|
transceiver, mid_generator_.GenerateString(),
|
|
|
|
|
/*is_create_offer=*/true);
|
|
|
|
|
} else {
|
|
|
|
|
mline_index = session_options->media_description_options.size();
|
|
|
|
|
session_options->media_description_options.push_back(
|
|
|
|
|
GetMediaDescriptionOptionsForTransceiver(
|
|
|
|
|
transceiver, mid_generator_.GenerateString(),
|
|
|
|
|
/*is_create_offer=*/true));
|
|
|
|
|
}
|
|
|
|
|
// See comment above for why CreateOffer changes the transceiver's state.
|
|
|
|
|
transceiver->set_mline_index(mline_index);
|
2022-05-09 08:30:09 +00:00
|
|
|
}
|
2020-10-01 10:23:33 +00:00
|
|
|
}
|
2023-03-15 20:39:42 +00:00
|
|
|
// Lastly, add a m-section if we have requested local data channels and an
|
|
|
|
|
// m section does not already exist.
|
2023-10-21 16:47:56 +02:00
|
|
|
if (!pc_->sctp_mid() && data_channel_controller()->HasDataChannels()) {
|
2023-05-09 09:41:03 +02:00
|
|
|
// Attempt to recycle a stopped m-line.
|
2023-10-21 16:47:56 +02:00
|
|
|
// TODO(crbug.com/1442604): sctp_mid() should return the mid if one was
|
2023-05-09 09:41:03 +02:00
|
|
|
// ever created but rejected.
|
|
|
|
|
bool recycled = false;
|
|
|
|
|
for (size_t i = 0; i < session_options->media_description_options.size();
|
|
|
|
|
i++) {
|
|
|
|
|
auto media_description = session_options->media_description_options[i];
|
|
|
|
|
if (media_description.type == cricket::MEDIA_TYPE_DATA &&
|
|
|
|
|
media_description.stopped) {
|
|
|
|
|
session_options->media_description_options[i] =
|
|
|
|
|
GetMediaDescriptionOptionsForActiveData(media_description.mid);
|
|
|
|
|
recycled = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!recycled) {
|
|
|
|
|
session_options->media_description_options.push_back(
|
|
|
|
|
GetMediaDescriptionOptionsForActiveData(
|
|
|
|
|
mid_generator_.GenerateString()));
|
|
|
|
|
}
|
2020-10-01 10:23:33 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::GetOptionsForAnswer(
|
|
|
|
|
const RTCOfferAnswerOptions& offer_answer_options,
|
|
|
|
|
cricket::MediaSessionOptions* session_options) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
ExtractSharedMediaSessionOptions(offer_answer_options, session_options);
|
|
|
|
|
|
|
|
|
|
if (IsUnifiedPlan()) {
|
|
|
|
|
GetOptionsForUnifiedPlanAnswer(offer_answer_options, session_options);
|
|
|
|
|
} else {
|
|
|
|
|
GetOptionsForPlanBAnswer(offer_answer_options, session_options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Apply ICE renomination flag.
|
|
|
|
|
for (auto& options : session_options->media_description_options) {
|
|
|
|
|
options.transport_options.enable_ice_renomination =
|
|
|
|
|
pc_->configuration()->enable_ice_renomination;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-05 13:08:41 +00:00
|
|
|
session_options->rtcp_cname = rtcp_cname_;
|
2020-10-01 10:23:33 +00:00
|
|
|
session_options->crypto_options = pc_->GetCryptoOptions();
|
|
|
|
|
session_options->pooled_ice_credentials =
|
2022-09-08 18:38:10 +02:00
|
|
|
context_->network_thread()->BlockingCall(
|
2021-01-18 09:24:33 +01:00
|
|
|
[this] { return port_allocator()->GetPooledIceCredentials(); });
|
2020-10-01 10:23:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::GetOptionsForPlanBAnswer(
|
|
|
|
|
const PeerConnectionInterface::RTCOfferAnswerOptions& offer_answer_options,
|
|
|
|
|
cricket::MediaSessionOptions* session_options) {
|
2022-05-23 14:57:47 +00:00
|
|
|
bool send_audio = false;
|
|
|
|
|
bool recv_audio = false;
|
|
|
|
|
bool send_video = false;
|
|
|
|
|
bool recv_video = false;
|
2020-10-01 10:23:33 +00:00
|
|
|
|
2022-05-23 14:57:47 +00:00
|
|
|
if (ConfiguredForMedia()) {
|
|
|
|
|
// Figure out transceiver directional preferences.
|
|
|
|
|
send_audio =
|
|
|
|
|
!rtp_manager()->GetAudioTransceiver()->internal()->senders().empty();
|
|
|
|
|
send_video =
|
|
|
|
|
!rtp_manager()->GetVideoTransceiver()->internal()->senders().empty();
|
2022-05-06 15:15:34 +00:00
|
|
|
|
2022-05-23 14:57:47 +00:00
|
|
|
// By default, generate sendrecv/recvonly m= sections. The direction is also
|
|
|
|
|
// restricted by the direction in the offer.
|
|
|
|
|
recv_audio = true;
|
|
|
|
|
recv_video = true;
|
|
|
|
|
|
|
|
|
|
// The "offer_to_receive_X" options allow those defaults to be overridden.
|
|
|
|
|
if (offer_answer_options.offer_to_receive_audio !=
|
|
|
|
|
RTCOfferAnswerOptions::kUndefined) {
|
|
|
|
|
recv_audio = (offer_answer_options.offer_to_receive_audio > 0);
|
|
|
|
|
}
|
|
|
|
|
if (offer_answer_options.offer_to_receive_video !=
|
|
|
|
|
RTCOfferAnswerOptions::kUndefined) {
|
|
|
|
|
recv_video = (offer_answer_options.offer_to_receive_video > 0);
|
|
|
|
|
}
|
2020-10-01 10:23:33 +00:00
|
|
|
}
|
|
|
|
|
|
2024-08-29 13:00:40 +00:00
|
|
|
std::optional<size_t> audio_index;
|
|
|
|
|
std::optional<size_t> video_index;
|
|
|
|
|
std::optional<size_t> data_index;
|
2020-10-01 10:23:33 +00:00
|
|
|
|
|
|
|
|
// Generate m= sections that match those in the offer.
|
|
|
|
|
// Note that mediasession.cc will handle intersection our preferred
|
|
|
|
|
// direction with the offered direction.
|
2020-10-05 13:08:41 +00:00
|
|
|
GenerateMediaDescriptionOptions(
|
2020-10-01 10:23:33 +00:00
|
|
|
remote_description(),
|
|
|
|
|
RtpTransceiverDirectionFromSendRecv(send_audio, recv_audio),
|
|
|
|
|
RtpTransceiverDirectionFromSendRecv(send_video, recv_video), &audio_index,
|
|
|
|
|
&video_index, &data_index, session_options);
|
|
|
|
|
|
|
|
|
|
cricket::MediaDescriptionOptions* audio_media_description_options =
|
|
|
|
|
!audio_index ? nullptr
|
|
|
|
|
: &session_options->media_description_options[*audio_index];
|
|
|
|
|
cricket::MediaDescriptionOptions* video_media_description_options =
|
|
|
|
|
!video_index ? nullptr
|
|
|
|
|
: &session_options->media_description_options[*video_index];
|
|
|
|
|
|
2022-05-23 14:57:47 +00:00
|
|
|
if (ConfiguredForMedia()) {
|
|
|
|
|
AddPlanBRtpSenderOptions(rtp_manager()->GetSendersInternal(),
|
|
|
|
|
audio_media_description_options,
|
|
|
|
|
video_media_description_options,
|
|
|
|
|
offer_answer_options.num_simulcast_layers);
|
|
|
|
|
}
|
2020-10-01 10:23:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::GetOptionsForUnifiedPlanAnswer(
|
|
|
|
|
const PeerConnectionInterface::RTCOfferAnswerOptions& offer_answer_options,
|
|
|
|
|
cricket::MediaSessionOptions* session_options) {
|
|
|
|
|
// Rules for generating an answer are dictated by JSEP sections 5.3.1 (Initial
|
|
|
|
|
// Answers) and 5.3.2 (Subsequent Answers).
|
|
|
|
|
RTC_DCHECK(remote_description());
|
|
|
|
|
RTC_DCHECK(remote_description()->GetType() == SdpType::kOffer);
|
|
|
|
|
for (const ContentInfo& content :
|
|
|
|
|
remote_description()->description()->contents()) {
|
|
|
|
|
cricket::MediaType media_type = content.media_description()->type();
|
|
|
|
|
if (media_type == cricket::MEDIA_TYPE_AUDIO ||
|
|
|
|
|
media_type == cricket::MEDIA_TYPE_VIDEO) {
|
2020-10-19 13:28:05 +00:00
|
|
|
auto transceiver = transceivers()->FindByMid(content.name);
|
2020-10-01 10:23:33 +00:00
|
|
|
if (transceiver) {
|
|
|
|
|
session_options->media_description_options.push_back(
|
|
|
|
|
GetMediaDescriptionOptionsForTransceiver(
|
2021-04-19 21:21:36 +00:00
|
|
|
transceiver->internal(), content.name,
|
2020-10-01 10:23:33 +00:00
|
|
|
/*is_create_offer=*/false));
|
|
|
|
|
} else {
|
|
|
|
|
// This should only happen with rejected transceivers.
|
|
|
|
|
RTC_DCHECK(content.rejected);
|
|
|
|
|
session_options->media_description_options.push_back(
|
|
|
|
|
cricket::MediaDescriptionOptions(media_type, content.name,
|
|
|
|
|
RtpTransceiverDirection::kInactive,
|
|
|
|
|
/*stopped=*/true));
|
|
|
|
|
}
|
2020-10-13 12:43:15 +02:00
|
|
|
} else if (media_type == cricket::MEDIA_TYPE_UNSUPPORTED) {
|
|
|
|
|
RTC_DCHECK(content.rejected);
|
|
|
|
|
session_options->media_description_options.push_back(
|
|
|
|
|
cricket::MediaDescriptionOptions(media_type, content.name,
|
|
|
|
|
RtpTransceiverDirection::kInactive,
|
|
|
|
|
/*stopped=*/true));
|
2020-10-01 10:23:33 +00:00
|
|
|
} else {
|
|
|
|
|
RTC_CHECK_EQ(cricket::MEDIA_TYPE_DATA, media_type);
|
|
|
|
|
// Reject all data sections if data channels are disabled.
|
|
|
|
|
// Reject a data section if it has already been rejected.
|
|
|
|
|
// Reject all data sections except for the first one.
|
2023-10-21 16:47:56 +02:00
|
|
|
if (content.rejected || content.name != *(pc_->sctp_mid())) {
|
2020-10-01 10:23:33 +00:00
|
|
|
session_options->media_description_options.push_back(
|
2020-10-05 13:08:41 +00:00
|
|
|
GetMediaDescriptionOptionsForRejectedData(content.name));
|
2020-10-01 10:23:33 +00:00
|
|
|
} else {
|
|
|
|
|
session_options->media_description_options.push_back(
|
2020-10-05 13:08:41 +00:00
|
|
|
GetMediaDescriptionOptionsForActiveData(content.name));
|
2020-10-01 10:23:33 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-01 16:47:23 +00:00
|
|
|
const char* SdpOfferAnswerHandler::SessionErrorToString(
|
|
|
|
|
SessionError error) const {
|
|
|
|
|
switch (error) {
|
|
|
|
|
case SessionError::kNone:
|
|
|
|
|
return "ERROR_NONE";
|
|
|
|
|
case SessionError::kContent:
|
|
|
|
|
return "ERROR_CONTENT";
|
|
|
|
|
case SessionError::kTransport:
|
|
|
|
|
return "ERROR_TRANSPORT";
|
|
|
|
|
}
|
2021-11-15 16:57:07 +01:00
|
|
|
RTC_DCHECK_NOTREACHED();
|
2020-10-01 16:47:23 +00:00
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string SdpOfferAnswerHandler::GetSessionErrorMsg() {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
rtc::StringBuilder desc;
|
|
|
|
|
desc << kSessionError << SessionErrorToString(session_error()) << ". ";
|
|
|
|
|
desc << kSessionErrorDesc << session_error_desc() << ".";
|
|
|
|
|
return desc.Release();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::SetSessionError(SessionError error,
|
|
|
|
|
const std::string& error_desc) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
if (error != session_error_) {
|
|
|
|
|
session_error_ = error;
|
|
|
|
|
session_error_desc_ = error_desc;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RTCError SdpOfferAnswerHandler::HandleLegacyOfferOptions(
|
|
|
|
|
const PeerConnectionInterface::RTCOfferAnswerOptions& options) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
RTC_DCHECK(IsUnifiedPlan());
|
|
|
|
|
|
|
|
|
|
if (options.offer_to_receive_audio == 0) {
|
|
|
|
|
RemoveRecvDirectionFromReceivingTransceiversOfType(
|
|
|
|
|
cricket::MEDIA_TYPE_AUDIO);
|
|
|
|
|
} else if (options.offer_to_receive_audio == 1) {
|
|
|
|
|
AddUpToOneReceivingTransceiverOfType(cricket::MEDIA_TYPE_AUDIO);
|
|
|
|
|
} else if (options.offer_to_receive_audio > 1) {
|
|
|
|
|
LOG_AND_RETURN_ERROR(RTCErrorType::UNSUPPORTED_PARAMETER,
|
|
|
|
|
"offer_to_receive_audio > 1 is not supported.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (options.offer_to_receive_video == 0) {
|
|
|
|
|
RemoveRecvDirectionFromReceivingTransceiversOfType(
|
|
|
|
|
cricket::MEDIA_TYPE_VIDEO);
|
|
|
|
|
} else if (options.offer_to_receive_video == 1) {
|
|
|
|
|
AddUpToOneReceivingTransceiverOfType(cricket::MEDIA_TYPE_VIDEO);
|
|
|
|
|
} else if (options.offer_to_receive_video > 1) {
|
|
|
|
|
LOG_AND_RETURN_ERROR(RTCErrorType::UNSUPPORTED_PARAMETER,
|
|
|
|
|
"offer_to_receive_video > 1 is not supported.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return RTCError::OK();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::RemoveRecvDirectionFromReceivingTransceiversOfType(
|
|
|
|
|
cricket::MediaType media_type) {
|
|
|
|
|
for (const auto& transceiver : GetReceivingTransceiversOfType(media_type)) {
|
|
|
|
|
RtpTransceiverDirection new_direction =
|
|
|
|
|
RtpTransceiverDirectionWithRecvSet(transceiver->direction(), false);
|
|
|
|
|
if (new_direction != transceiver->direction()) {
|
|
|
|
|
RTC_LOG(LS_INFO) << "Changing " << cricket::MediaTypeToString(media_type)
|
|
|
|
|
<< " transceiver (MID="
|
|
|
|
|
<< transceiver->mid().value_or("<not set>") << ") from "
|
|
|
|
|
<< RtpTransceiverDirectionToString(
|
|
|
|
|
transceiver->direction())
|
|
|
|
|
<< " to "
|
|
|
|
|
<< RtpTransceiverDirectionToString(new_direction)
|
|
|
|
|
<< " since CreateOffer specified offer_to_receive=0";
|
|
|
|
|
transceiver->internal()->set_direction(new_direction);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::AddUpToOneReceivingTransceiverOfType(
|
|
|
|
|
cricket::MediaType media_type) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
if (GetReceivingTransceiversOfType(media_type).empty()) {
|
|
|
|
|
RTC_LOG(LS_INFO)
|
|
|
|
|
<< "Adding one recvonly " << cricket::MediaTypeToString(media_type)
|
|
|
|
|
<< " transceiver since CreateOffer specified offer_to_receive=1";
|
|
|
|
|
RtpTransceiverInit init;
|
|
|
|
|
init.direction = RtpTransceiverDirection::kRecvOnly;
|
|
|
|
|
pc_->AddTransceiver(media_type, nullptr, init,
|
|
|
|
|
/*update_negotiation_needed=*/false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>>
|
|
|
|
|
SdpOfferAnswerHandler::GetReceivingTransceiversOfType(
|
|
|
|
|
cricket::MediaType media_type) {
|
|
|
|
|
std::vector<
|
|
|
|
|
rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>>
|
|
|
|
|
receiving_transceivers;
|
2020-10-19 13:28:05 +00:00
|
|
|
for (const auto& transceiver : transceivers()->List()) {
|
2020-10-01 16:47:23 +00:00
|
|
|
if (!transceiver->stopped() && transceiver->media_type() == media_type &&
|
|
|
|
|
RtpTransceiverDirectionHasRecv(transceiver->direction())) {
|
|
|
|
|
receiving_transceivers.push_back(transceiver);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return receiving_transceivers;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::ProcessRemovalOfRemoteTrack(
|
|
|
|
|
rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
|
|
|
|
|
transceiver,
|
|
|
|
|
std::vector<rtc::scoped_refptr<RtpTransceiverInterface>>* remove_list,
|
|
|
|
|
std::vector<rtc::scoped_refptr<MediaStreamInterface>>* removed_streams) {
|
|
|
|
|
RTC_DCHECK(transceiver->mid());
|
|
|
|
|
RTC_LOG(LS_INFO) << "Processing the removal of a track for MID="
|
|
|
|
|
<< *transceiver->mid();
|
|
|
|
|
std::vector<rtc::scoped_refptr<MediaStreamInterface>> previous_streams =
|
|
|
|
|
transceiver->internal()->receiver_internal()->streams();
|
|
|
|
|
// This will remove the remote track from the streams.
|
|
|
|
|
transceiver->internal()->receiver_internal()->set_stream_ids({});
|
|
|
|
|
remove_list->push_back(transceiver);
|
|
|
|
|
RemoveRemoteStreamsIfEmpty(previous_streams, removed_streams);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::RemoveRemoteStreamsIfEmpty(
|
|
|
|
|
const std::vector<rtc::scoped_refptr<MediaStreamInterface>>& remote_streams,
|
|
|
|
|
std::vector<rtc::scoped_refptr<MediaStreamInterface>>* removed_streams) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
// TODO(https://crbug.com/webrtc/9480): When we use stream IDs instead of
|
|
|
|
|
// streams, see if the stream was removed by checking if this was the last
|
|
|
|
|
// receiver with that stream ID.
|
|
|
|
|
for (const auto& remote_stream : remote_streams) {
|
|
|
|
|
if (remote_stream->GetAudioTracks().empty() &&
|
|
|
|
|
remote_stream->GetVideoTracks().empty()) {
|
2022-04-20 14:26:50 +02:00
|
|
|
remote_streams_->RemoveStream(remote_stream.get());
|
2020-10-01 16:47:23 +00:00
|
|
|
removed_streams->push_back(remote_stream);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::RemoveSenders(cricket::MediaType media_type) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
UpdateLocalSenders(std::vector<cricket::StreamParams>(), media_type);
|
|
|
|
|
UpdateRemoteSendersList(std::vector<cricket::StreamParams>(), false,
|
|
|
|
|
media_type, nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::UpdateLocalSenders(
|
|
|
|
|
const std::vector<cricket::StreamParams>& streams,
|
|
|
|
|
cricket::MediaType media_type) {
|
2021-06-07 13:30:46 +02:00
|
|
|
TRACE_EVENT0("webrtc", "SdpOfferAnswerHandler::UpdateLocalSenders");
|
2020-10-01 16:47:23 +00:00
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
2020-10-19 13:28:05 +00:00
|
|
|
std::vector<RtpSenderInfo>* current_senders =
|
|
|
|
|
rtp_manager()->GetLocalSenderInfos(media_type);
|
2020-10-01 16:47:23 +00:00
|
|
|
|
|
|
|
|
// Find removed tracks. I.e., tracks where the track id, stream id or ssrc
|
|
|
|
|
// don't match the new StreamParam.
|
|
|
|
|
for (auto sender_it = current_senders->begin();
|
|
|
|
|
sender_it != current_senders->end();
|
|
|
|
|
/* incremented manually */) {
|
2020-10-19 13:28:05 +00:00
|
|
|
const RtpSenderInfo& info = *sender_it;
|
2020-10-01 16:47:23 +00:00
|
|
|
const cricket::StreamParams* params =
|
|
|
|
|
cricket::GetStreamBySsrc(streams, info.first_ssrc);
|
|
|
|
|
if (!params || params->id != info.sender_id ||
|
|
|
|
|
params->first_stream_id() != info.stream_id) {
|
2020-10-19 13:28:05 +00:00
|
|
|
rtp_manager()->OnLocalSenderRemoved(info, media_type);
|
2020-10-01 16:47:23 +00:00
|
|
|
sender_it = current_senders->erase(sender_it);
|
|
|
|
|
} else {
|
|
|
|
|
++sender_it;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find new and active senders.
|
|
|
|
|
for (const cricket::StreamParams& params : streams) {
|
2021-08-10 01:22:31 +02:00
|
|
|
// The sync_label is the MediaStream label and the `stream.id` is the
|
2020-10-01 16:47:23 +00:00
|
|
|
// sender id.
|
|
|
|
|
const std::string& stream_id = params.first_stream_id();
|
|
|
|
|
const std::string& sender_id = params.id;
|
|
|
|
|
uint32_t ssrc = params.first_ssrc();
|
2020-10-19 13:28:05 +00:00
|
|
|
const RtpSenderInfo* sender_info =
|
|
|
|
|
rtp_manager()->FindSenderInfo(*current_senders, stream_id, sender_id);
|
2020-10-01 16:47:23 +00:00
|
|
|
if (!sender_info) {
|
2020-10-19 13:28:05 +00:00
|
|
|
current_senders->push_back(RtpSenderInfo(stream_id, sender_id, ssrc));
|
|
|
|
|
rtp_manager()->OnLocalSenderAdded(current_senders->back(), media_type);
|
2020-10-01 16:47:23 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::UpdateRemoteSendersList(
|
|
|
|
|
const cricket::StreamParamsVec& streams,
|
|
|
|
|
bool default_sender_needed,
|
|
|
|
|
cricket::MediaType media_type,
|
|
|
|
|
StreamCollection* new_streams) {
|
2021-06-07 13:30:46 +02:00
|
|
|
TRACE_EVENT0("webrtc", "SdpOfferAnswerHandler::UpdateRemoteSendersList");
|
2020-10-01 16:47:23 +00:00
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
RTC_DCHECK(!IsUnifiedPlan());
|
|
|
|
|
|
2020-10-19 13:28:05 +00:00
|
|
|
std::vector<RtpSenderInfo>* current_senders =
|
|
|
|
|
rtp_manager()->GetRemoteSenderInfos(media_type);
|
2020-10-01 16:47:23 +00:00
|
|
|
|
|
|
|
|
// Find removed senders. I.e., senders where the sender id or ssrc don't match
|
|
|
|
|
// the new StreamParam.
|
|
|
|
|
for (auto sender_it = current_senders->begin();
|
|
|
|
|
sender_it != current_senders->end();
|
|
|
|
|
/* incremented manually */) {
|
2020-10-19 13:28:05 +00:00
|
|
|
const RtpSenderInfo& info = *sender_it;
|
2020-10-01 16:47:23 +00:00
|
|
|
const cricket::StreamParams* params =
|
|
|
|
|
cricket::GetStreamBySsrc(streams, info.first_ssrc);
|
|
|
|
|
std::string params_stream_id;
|
|
|
|
|
if (params) {
|
|
|
|
|
params_stream_id =
|
|
|
|
|
(!params->first_stream_id().empty() ? params->first_stream_id()
|
|
|
|
|
: kDefaultStreamId);
|
|
|
|
|
}
|
|
|
|
|
bool sender_exists = params && params->id == info.sender_id &&
|
|
|
|
|
params_stream_id == info.stream_id;
|
|
|
|
|
// If this is a default track, and we still need it, don't remove it.
|
|
|
|
|
if ((info.stream_id == kDefaultStreamId && default_sender_needed) ||
|
|
|
|
|
sender_exists) {
|
|
|
|
|
++sender_it;
|
|
|
|
|
} else {
|
2020-10-19 13:28:05 +00:00
|
|
|
rtp_manager()->OnRemoteSenderRemoved(
|
|
|
|
|
info, remote_streams_->find(info.stream_id), media_type);
|
2020-10-01 16:47:23 +00:00
|
|
|
sender_it = current_senders->erase(sender_it);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find new and active senders.
|
|
|
|
|
for (const cricket::StreamParams& params : streams) {
|
|
|
|
|
if (!params.has_ssrcs()) {
|
|
|
|
|
// The remote endpoint has streams, but didn't signal ssrcs. For an active
|
|
|
|
|
// sender, this means it is coming from a Unified Plan endpoint,so we just
|
|
|
|
|
// create a default.
|
|
|
|
|
default_sender_needed = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-10 01:22:31 +02:00
|
|
|
// `params.id` is the sender id and the stream id uses the first of
|
|
|
|
|
// `params.stream_ids`. The remote description could come from a Unified
|
2020-10-01 16:47:23 +00:00
|
|
|
// Plan endpoint, with multiple or no stream_ids() signaled. Since this is
|
|
|
|
|
// not supported in Plan B, we just take the first here and create the
|
|
|
|
|
// default stream ID if none is specified.
|
|
|
|
|
const std::string& stream_id =
|
|
|
|
|
(!params.first_stream_id().empty() ? params.first_stream_id()
|
|
|
|
|
: kDefaultStreamId);
|
|
|
|
|
const std::string& sender_id = params.id;
|
|
|
|
|
uint32_t ssrc = params.first_ssrc();
|
|
|
|
|
|
2022-01-04 15:20:03 +01:00
|
|
|
rtc::scoped_refptr<MediaStreamInterface> stream(
|
|
|
|
|
remote_streams_->find(stream_id));
|
2020-10-01 16:47:23 +00:00
|
|
|
if (!stream) {
|
|
|
|
|
// This is a new MediaStream. Create a new remote MediaStream.
|
|
|
|
|
stream = MediaStreamProxy::Create(rtc::Thread::Current(),
|
|
|
|
|
MediaStream::Create(stream_id));
|
2020-10-09 11:42:17 +00:00
|
|
|
remote_streams_->AddStream(stream);
|
2020-10-01 16:47:23 +00:00
|
|
|
new_streams->AddStream(stream);
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-19 13:28:05 +00:00
|
|
|
const RtpSenderInfo* sender_info =
|
|
|
|
|
rtp_manager()->FindSenderInfo(*current_senders, stream_id, sender_id);
|
2020-10-01 16:47:23 +00:00
|
|
|
if (!sender_info) {
|
2020-10-19 13:28:05 +00:00
|
|
|
current_senders->push_back(RtpSenderInfo(stream_id, sender_id, ssrc));
|
2022-04-20 14:26:50 +02:00
|
|
|
rtp_manager()->OnRemoteSenderAdded(current_senders->back(), stream.get(),
|
2020-10-19 13:28:05 +00:00
|
|
|
media_type);
|
2020-10-01 16:47:23 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add default sender if necessary.
|
|
|
|
|
if (default_sender_needed) {
|
2022-01-04 15:20:03 +01:00
|
|
|
rtc::scoped_refptr<MediaStreamInterface> default_stream(
|
|
|
|
|
remote_streams_->find(kDefaultStreamId));
|
2020-10-01 16:47:23 +00:00
|
|
|
if (!default_stream) {
|
|
|
|
|
// Create the new default MediaStream.
|
|
|
|
|
default_stream = MediaStreamProxy::Create(
|
|
|
|
|
rtc::Thread::Current(), MediaStream::Create(kDefaultStreamId));
|
2020-10-09 11:42:17 +00:00
|
|
|
remote_streams_->AddStream(default_stream);
|
2020-10-01 16:47:23 +00:00
|
|
|
new_streams->AddStream(default_stream);
|
|
|
|
|
}
|
|
|
|
|
std::string default_sender_id = (media_type == cricket::MEDIA_TYPE_AUDIO)
|
|
|
|
|
? kDefaultAudioSenderId
|
|
|
|
|
: kDefaultVideoSenderId;
|
2020-10-19 13:28:05 +00:00
|
|
|
const RtpSenderInfo* default_sender_info = rtp_manager()->FindSenderInfo(
|
|
|
|
|
*current_senders, kDefaultStreamId, default_sender_id);
|
2020-10-01 16:47:23 +00:00
|
|
|
if (!default_sender_info) {
|
2020-10-19 13:28:05 +00:00
|
|
|
current_senders->push_back(
|
|
|
|
|
RtpSenderInfo(kDefaultStreamId, default_sender_id, /*ssrc=*/0));
|
|
|
|
|
rtp_manager()->OnRemoteSenderAdded(current_senders->back(),
|
2022-04-20 14:26:50 +02:00
|
|
|
default_stream.get(), media_type);
|
2020-10-01 16:47:23 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-25 13:44:55 -08:00
|
|
|
void SdpOfferAnswerHandler::EnableSending() {
|
2021-06-07 13:30:46 +02:00
|
|
|
TRACE_EVENT0("webrtc", "SdpOfferAnswerHandler::EnableSending");
|
2021-01-25 13:44:55 -08:00
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
2022-05-23 14:57:47 +00:00
|
|
|
if (!ConfiguredForMedia()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-04-19 21:21:36 +00:00
|
|
|
for (const auto& transceiver : transceivers()->ListInternal()) {
|
|
|
|
|
cricket::ChannelInterface* channel = transceiver->channel();
|
2021-04-26 10:20:19 +02:00
|
|
|
if (channel) {
|
2021-01-25 13:44:55 -08:00
|
|
|
channel->Enable(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-01 16:47:23 +00:00
|
|
|
RTCError SdpOfferAnswerHandler::PushdownMediaDescription(
|
|
|
|
|
SdpType type,
|
2021-04-26 21:04:26 +02:00
|
|
|
cricket::ContentSource source,
|
|
|
|
|
const std::map<std::string, const cricket::ContentGroup*>&
|
|
|
|
|
bundle_groups_by_mid) {
|
2021-06-07 13:30:46 +02:00
|
|
|
TRACE_EVENT0("webrtc", "SdpOfferAnswerHandler::PushdownMediaDescription");
|
2020-10-01 16:47:23 +00:00
|
|
|
const SessionDescriptionInterface* sdesc =
|
|
|
|
|
(source == cricket::CS_LOCAL ? local_description()
|
|
|
|
|
: remote_description());
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
RTC_DCHECK(sdesc);
|
|
|
|
|
|
2022-05-23 14:57:47 +00:00
|
|
|
if (ConfiguredForMedia()) {
|
2022-09-08 18:38:10 +02:00
|
|
|
// Note: This will perform a BlockingCall over to the worker thread, which
|
|
|
|
|
// we'll also do in a loop below.
|
2022-05-23 14:57:47 +00:00
|
|
|
if (!UpdatePayloadTypeDemuxingState(source, bundle_groups_by_mid)) {
|
|
|
|
|
// Note that this is never expected to fail, since RtpDemuxer doesn't
|
|
|
|
|
// return an error when changing payload type demux criteria, which is all
|
|
|
|
|
// this does.
|
2023-07-18 11:03:39 +02:00
|
|
|
LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR,
|
|
|
|
|
"Failed to update payload type demuxing state.");
|
2022-05-09 08:30:09 +00:00
|
|
|
}
|
2022-05-06 15:15:34 +00:00
|
|
|
|
2022-05-23 14:57:47 +00:00
|
|
|
// Push down the new SDP media section for each audio/video transceiver.
|
|
|
|
|
auto rtp_transceivers = transceivers()->ListInternal();
|
|
|
|
|
std::vector<
|
|
|
|
|
std::pair<cricket::ChannelInterface*, const MediaContentDescription*>>
|
|
|
|
|
channels;
|
|
|
|
|
for (const auto& transceiver : rtp_transceivers) {
|
|
|
|
|
const ContentInfo* content_info =
|
|
|
|
|
FindMediaSectionForTransceiver(transceiver, sdesc);
|
|
|
|
|
cricket::ChannelInterface* channel = transceiver->channel();
|
|
|
|
|
if (!channel || !content_info || content_info->rejected) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const MediaContentDescription* content_desc =
|
|
|
|
|
content_info->media_description();
|
|
|
|
|
if (!content_desc) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
transceiver->OnNegotiationUpdate(type, content_desc);
|
|
|
|
|
channels.push_back(std::make_pair(channel, content_desc));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This for-loop of invokes helps audio impairment during re-negotiations.
|
|
|
|
|
// One of the causes is that downstairs decoder creation is synchronous at
|
|
|
|
|
// the moment, and that a decoder is created for each codec listed in the
|
|
|
|
|
// SDP.
|
|
|
|
|
//
|
|
|
|
|
// TODO(bugs.webrtc.org/12840): consider merging the invokes again after
|
|
|
|
|
// these projects have shipped:
|
|
|
|
|
// - bugs.webrtc.org/12462
|
|
|
|
|
// - crbug.com/1157227
|
|
|
|
|
// - crbug.com/1187289
|
|
|
|
|
for (const auto& entry : channels) {
|
|
|
|
|
std::string error;
|
2022-09-08 18:38:10 +02:00
|
|
|
bool success = context_->worker_thread()->BlockingCall([&]() {
|
2022-05-23 14:57:47 +00:00
|
|
|
return (source == cricket::CS_LOCAL)
|
|
|
|
|
? entry.first->SetLocalContent(entry.second, type, error)
|
|
|
|
|
: entry.first->SetRemoteContent(entry.second, type, error);
|
|
|
|
|
});
|
|
|
|
|
if (!success) {
|
2023-07-18 11:03:39 +02:00
|
|
|
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, error);
|
2022-05-23 14:57:47 +00:00
|
|
|
}
|
2020-10-01 16:47:23 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Need complete offer/answer with an SCTP m= section before starting SCTP,
|
|
|
|
|
// according to https://tools.ietf.org/html/draft-ietf-mmusic-sctp-sdp-19
|
|
|
|
|
if (pc_->sctp_mid() && local_description() && remote_description()) {
|
|
|
|
|
auto local_sctp_description = cricket::GetFirstSctpDataContentDescription(
|
|
|
|
|
local_description()->description());
|
|
|
|
|
auto remote_sctp_description = cricket::GetFirstSctpDataContentDescription(
|
|
|
|
|
remote_description()->description());
|
2021-02-10 13:05:44 +01:00
|
|
|
if (local_sctp_description && remote_sctp_description) {
|
2020-10-01 16:47:23 +00:00
|
|
|
int max_message_size;
|
|
|
|
|
// A remote max message size of zero means "any size supported".
|
|
|
|
|
// We configure the connection with our own max message size.
|
|
|
|
|
if (remote_sctp_description->max_message_size() == 0) {
|
|
|
|
|
max_message_size = local_sctp_description->max_message_size();
|
|
|
|
|
} else {
|
|
|
|
|
max_message_size =
|
|
|
|
|
std::min(local_sctp_description->max_message_size(),
|
|
|
|
|
remote_sctp_description->max_message_size());
|
|
|
|
|
}
|
2021-02-10 13:05:44 +01:00
|
|
|
pc_->StartSctpTransport(local_sctp_description->port(),
|
|
|
|
|
remote_sctp_description->port(),
|
|
|
|
|
max_message_size);
|
2020-10-01 16:47:23 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return RTCError::OK();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RTCError SdpOfferAnswerHandler::PushdownTransportDescription(
|
|
|
|
|
cricket::ContentSource source,
|
|
|
|
|
SdpType type) {
|
2021-06-07 13:30:46 +02:00
|
|
|
TRACE_EVENT0("webrtc", "SdpOfferAnswerHandler::PushdownTransportDescription");
|
2020-10-01 16:47:23 +00:00
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
|
|
|
|
|
if (source == cricket::CS_LOCAL) {
|
|
|
|
|
const SessionDescriptionInterface* sdesc = local_description();
|
|
|
|
|
RTC_DCHECK(sdesc);
|
2024-01-11 22:15:27 +01:00
|
|
|
const auto* remote = remote_description();
|
|
|
|
|
return transport_controller_s()->SetLocalDescription(
|
|
|
|
|
type, sdesc->description(), remote ? remote->description() : nullptr);
|
2020-10-01 16:47:23 +00:00
|
|
|
} else {
|
|
|
|
|
const SessionDescriptionInterface* sdesc = remote_description();
|
|
|
|
|
RTC_DCHECK(sdesc);
|
2024-01-11 22:15:27 +01:00
|
|
|
const auto* local = local_description();
|
|
|
|
|
return transport_controller_s()->SetRemoteDescription(
|
|
|
|
|
type, local ? local->description() : nullptr, sdesc->description());
|
2020-10-01 16:47:23 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::RemoveStoppedTransceivers() {
|
2021-06-07 13:30:46 +02:00
|
|
|
TRACE_EVENT0("webrtc", "SdpOfferAnswerHandler::RemoveStoppedTransceivers");
|
2020-10-01 16:47:23 +00:00
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
// 3.2.10.1: For each transceiver in the connection's set of transceivers
|
|
|
|
|
// run the following steps:
|
|
|
|
|
if (!IsUnifiedPlan())
|
|
|
|
|
return;
|
2022-05-23 14:57:47 +00:00
|
|
|
if (!ConfiguredForMedia()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-10-01 16:47:23 +00:00
|
|
|
// Traverse a copy of the transceiver list.
|
2020-10-19 13:28:05 +00:00
|
|
|
auto transceiver_list = transceivers()->List();
|
2020-10-01 16:47:23 +00:00
|
|
|
for (auto transceiver : transceiver_list) {
|
|
|
|
|
// 3.2.10.1.1: If transceiver is stopped, associated with an m= section
|
|
|
|
|
// and the associated m= section is rejected in
|
|
|
|
|
// connection.[[CurrentLocalDescription]] or
|
|
|
|
|
// connection.[[CurrentRemoteDescription]], remove the
|
|
|
|
|
// transceiver from the connection's set of transceivers.
|
|
|
|
|
if (!transceiver->stopped()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2021-04-19 21:21:36 +00:00
|
|
|
const ContentInfo* local_content = FindMediaSectionForTransceiver(
|
|
|
|
|
transceiver->internal(), local_description());
|
|
|
|
|
const ContentInfo* remote_content = FindMediaSectionForTransceiver(
|
|
|
|
|
transceiver->internal(), remote_description());
|
2020-10-01 16:47:23 +00:00
|
|
|
if ((local_content && local_content->rejected) ||
|
|
|
|
|
(remote_content && remote_content->rejected)) {
|
|
|
|
|
RTC_LOG(LS_INFO) << "Dissociating transceiver"
|
2021-04-16 14:28:26 +02:00
|
|
|
" since the media section is being recycled.";
|
2024-08-29 13:00:40 +00:00
|
|
|
transceiver->internal()->set_mid(std::nullopt);
|
|
|
|
|
transceiver->internal()->set_mline_index(std::nullopt);
|
2021-04-16 14:28:26 +02:00
|
|
|
} else if (!local_content && !remote_content) {
|
2020-10-01 16:47:23 +00:00
|
|
|
// TODO(bugs.webrtc.org/11973): Consider if this should be removed already
|
|
|
|
|
// See https://github.com/w3c/webrtc-pc/issues/2576
|
|
|
|
|
RTC_LOG(LS_INFO)
|
|
|
|
|
<< "Dropping stopped transceiver that was never associated";
|
|
|
|
|
}
|
2021-04-16 14:28:26 +02:00
|
|
|
transceivers()->Remove(transceiver);
|
2020-10-01 16:47:23 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::RemoveUnusedChannels(
|
|
|
|
|
const SessionDescription* desc) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
2022-05-23 14:57:47 +00:00
|
|
|
if (ConfiguredForMedia()) {
|
|
|
|
|
// Destroy video channel first since it may have a pointer to the
|
|
|
|
|
// voice channel.
|
|
|
|
|
const cricket::ContentInfo* video_info =
|
|
|
|
|
cricket::GetFirstVideoContent(desc);
|
|
|
|
|
if (!video_info || video_info->rejected) {
|
|
|
|
|
rtp_manager()->GetVideoTransceiver()->internal()->ClearChannel();
|
|
|
|
|
}
|
2020-10-01 16:47:23 +00:00
|
|
|
|
2022-05-23 14:57:47 +00:00
|
|
|
const cricket::ContentInfo* audio_info =
|
|
|
|
|
cricket::GetFirstAudioContent(desc);
|
|
|
|
|
if (!audio_info || audio_info->rejected) {
|
|
|
|
|
rtp_manager()->GetAudioTransceiver()->internal()->ClearChannel();
|
|
|
|
|
}
|
2020-10-01 16:47:23 +00:00
|
|
|
}
|
|
|
|
|
const cricket::ContentInfo* data_info = cricket::GetFirstDataContent(desc);
|
2021-06-29 14:58:23 +02:00
|
|
|
if (!data_info) {
|
|
|
|
|
RTCError error(RTCErrorType::OPERATION_ERROR_WITH_DATA,
|
|
|
|
|
"No data channel section in the description.");
|
|
|
|
|
error.set_error_detail(RTCErrorDetailType::DATA_CHANNEL_FAILURE);
|
2023-10-21 16:47:56 +02:00
|
|
|
pc_->DestroyDataChannelTransport(error);
|
2021-06-29 14:58:23 +02:00
|
|
|
} else if (data_info->rejected) {
|
|
|
|
|
rtc::StringBuilder sb;
|
|
|
|
|
sb << "Rejected data channel with mid=" << data_info->name << ".";
|
|
|
|
|
|
|
|
|
|
RTCError error(RTCErrorType::OPERATION_ERROR_WITH_DATA, sb.Release());
|
|
|
|
|
error.set_error_detail(RTCErrorDetailType::DATA_CHANNEL_FAILURE);
|
2023-10-21 16:47:56 +02:00
|
|
|
pc_->DestroyDataChannelTransport(error);
|
2020-10-01 16:47:23 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-05 13:08:41 +00:00
|
|
|
void SdpOfferAnswerHandler::UpdateEndedRemoteMediaStreams() {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
std::vector<rtc::scoped_refptr<MediaStreamInterface>> streams_to_remove;
|
2020-10-09 11:42:17 +00:00
|
|
|
for (size_t i = 0; i < remote_streams_->count(); ++i) {
|
|
|
|
|
MediaStreamInterface* stream = remote_streams_->at(i);
|
2020-10-05 13:08:41 +00:00
|
|
|
if (stream->GetAudioTracks().empty() && stream->GetVideoTracks().empty()) {
|
2022-01-04 15:20:03 +01:00
|
|
|
streams_to_remove.push_back(
|
|
|
|
|
rtc::scoped_refptr<MediaStreamInterface>(stream));
|
2020-10-05 13:08:41 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (auto& stream : streams_to_remove) {
|
2022-04-20 14:26:50 +02:00
|
|
|
remote_streams_->RemoveStream(stream.get());
|
2020-10-05 13:08:41 +00:00
|
|
|
pc_->Observer()->OnRemoveStream(std::move(stream));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-17 19:19:56 +01:00
|
|
|
bool SdpOfferAnswerHandler::UseCandidatesInRemoteDescription() {
|
2020-10-05 13:08:41 +00:00
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
2022-01-17 19:19:56 +01:00
|
|
|
auto* remote_desc = remote_description();
|
2020-10-05 13:08:41 +00:00
|
|
|
if (!remote_desc) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
bool ret = true;
|
|
|
|
|
|
|
|
|
|
for (size_t m = 0; m < remote_desc->number_of_mediasections(); ++m) {
|
|
|
|
|
const IceCandidateCollection* candidates = remote_desc->candidates(m);
|
|
|
|
|
for (size_t n = 0; n < candidates->count(); ++n) {
|
|
|
|
|
const IceCandidateInterface* candidate = candidates->at(n);
|
|
|
|
|
bool valid = false;
|
|
|
|
|
if (!ReadyToUseRemoteCandidate(candidate, remote_desc, &valid)) {
|
|
|
|
|
if (valid) {
|
|
|
|
|
RTC_LOG(LS_INFO)
|
2022-01-17 19:19:56 +01:00
|
|
|
<< "UseCandidatesInRemoteDescription: Not ready to use "
|
2020-10-05 13:08:41 +00:00
|
|
|
"candidate.";
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
ret = UseCandidate(candidate);
|
|
|
|
|
if (!ret) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool SdpOfferAnswerHandler::UseCandidate(
|
|
|
|
|
const IceCandidateInterface* candidate) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
2021-02-08 18:57:04 +01:00
|
|
|
|
|
|
|
|
rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls;
|
|
|
|
|
|
2020-10-05 13:08:41 +00:00
|
|
|
RTCErrorOr<const cricket::ContentInfo*> result =
|
|
|
|
|
FindContentInfo(remote_description(), candidate);
|
2021-02-08 18:57:04 +01:00
|
|
|
if (!result.ok())
|
2020-10-05 13:08:41 +00:00
|
|
|
return false;
|
2021-02-08 18:57:04 +01:00
|
|
|
|
|
|
|
|
const cricket::Candidate& c = candidate->candidate();
|
|
|
|
|
RTCError error = cricket::VerifyCandidate(c);
|
2021-02-12 13:16:26 +01:00
|
|
|
if (!error.ok()) {
|
|
|
|
|
RTC_LOG(LS_WARNING) << "Invalid candidate: " << c.ToString();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2021-02-08 18:57:04 +01:00
|
|
|
|
|
|
|
|
pc_->AddRemoteCandidate(result.value()->name, c);
|
|
|
|
|
|
2020-10-05 13:08:41 +00:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We need to check the local/remote description for the Transport instead of
|
|
|
|
|
// the session, because a new Transport added during renegotiation may have
|
|
|
|
|
// them unset while the session has them set from the previous negotiation.
|
|
|
|
|
// Not doing so may trigger the auto generation of transport description and
|
|
|
|
|
// mess up DTLS identity information, ICE credential, etc.
|
|
|
|
|
bool SdpOfferAnswerHandler::ReadyToUseRemoteCandidate(
|
|
|
|
|
const IceCandidateInterface* candidate,
|
|
|
|
|
const SessionDescriptionInterface* remote_desc,
|
|
|
|
|
bool* valid) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
*valid = true;
|
|
|
|
|
|
|
|
|
|
const SessionDescriptionInterface* current_remote_desc =
|
|
|
|
|
remote_desc ? remote_desc : remote_description();
|
|
|
|
|
|
|
|
|
|
if (!current_remote_desc) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RTCErrorOr<const cricket::ContentInfo*> result =
|
|
|
|
|
FindContentInfo(current_remote_desc, candidate);
|
|
|
|
|
if (!result.ok()) {
|
|
|
|
|
RTC_LOG(LS_ERROR) << "ReadyToUseRemoteCandidate: Invalid candidate. "
|
|
|
|
|
<< result.error().message();
|
|
|
|
|
|
|
|
|
|
*valid = false;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-02 12:33:39 +02:00
|
|
|
return true;
|
2020-10-05 13:08:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RTCErrorOr<const cricket::ContentInfo*> SdpOfferAnswerHandler::FindContentInfo(
|
|
|
|
|
const SessionDescriptionInterface* description,
|
|
|
|
|
const IceCandidateInterface* candidate) {
|
2021-02-26 09:23:53 +01:00
|
|
|
if (!candidate->sdp_mid().empty()) {
|
2020-10-05 13:08:41 +00:00
|
|
|
auto& contents = description->description()->contents();
|
|
|
|
|
auto it = absl::c_find_if(
|
|
|
|
|
contents, [candidate](const cricket::ContentInfo& content_info) {
|
|
|
|
|
return content_info.mid() == candidate->sdp_mid();
|
|
|
|
|
});
|
|
|
|
|
if (it == contents.end()) {
|
2023-07-18 11:03:39 +02:00
|
|
|
LOG_AND_RETURN_ERROR(
|
2020-10-05 13:08:41 +00:00
|
|
|
RTCErrorType::INVALID_PARAMETER,
|
|
|
|
|
"Mid " + candidate->sdp_mid() +
|
|
|
|
|
" specified but no media section with that mid found.");
|
|
|
|
|
} else {
|
|
|
|
|
return &*it;
|
|
|
|
|
}
|
2021-02-26 09:23:53 +01:00
|
|
|
} else if (candidate->sdp_mline_index() >= 0) {
|
|
|
|
|
size_t mediacontent_index =
|
|
|
|
|
static_cast<size_t>(candidate->sdp_mline_index());
|
|
|
|
|
size_t content_size = description->description()->contents().size();
|
|
|
|
|
if (mediacontent_index < content_size) {
|
|
|
|
|
return &description->description()->contents()[mediacontent_index];
|
|
|
|
|
} else {
|
2023-07-18 11:03:39 +02:00
|
|
|
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_RANGE,
|
|
|
|
|
"Media line index (" +
|
|
|
|
|
rtc::ToString(candidate->sdp_mline_index()) +
|
|
|
|
|
") out of range (number of mlines: " +
|
|
|
|
|
rtc::ToString(content_size) + ").");
|
2021-02-26 09:23:53 +01:00
|
|
|
}
|
2020-10-05 13:08:41 +00:00
|
|
|
}
|
|
|
|
|
|
2023-07-18 11:03:39 +02:00
|
|
|
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
|
|
|
|
|
"Neither sdp_mline_index nor sdp_mid specified.");
|
2020-10-05 13:08:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RTCError SdpOfferAnswerHandler::CreateChannels(const SessionDescription& desc) {
|
2021-06-07 13:30:46 +02:00
|
|
|
TRACE_EVENT0("webrtc", "SdpOfferAnswerHandler::CreateChannels");
|
2020-10-05 13:08:41 +00:00
|
|
|
// Creating the media channels. Transports should already have been created
|
|
|
|
|
// at this point.
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
const cricket::ContentInfo* voice = cricket::GetFirstAudioContent(&desc);
|
|
|
|
|
if (voice && !voice->rejected &&
|
2020-10-19 13:28:05 +00:00
|
|
|
!rtp_manager()->GetAudioTransceiver()->internal()->channel()) {
|
2022-05-04 10:32:30 +00:00
|
|
|
auto error =
|
|
|
|
|
rtp_manager()->GetAudioTransceiver()->internal()->CreateChannel(
|
|
|
|
|
voice->name, pc_->call_ptr(), pc_->configuration()->media_config,
|
|
|
|
|
pc_->SrtpRequired(), pc_->GetCryptoOptions(), audio_options(),
|
|
|
|
|
video_options(), video_bitrate_allocator_factory_.get(),
|
|
|
|
|
[&](absl::string_view mid) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(network_thread());
|
|
|
|
|
return transport_controller_n()->GetRtpTransport(mid);
|
|
|
|
|
});
|
|
|
|
|
if (!error.ok()) {
|
|
|
|
|
return error;
|
2020-10-05 13:08:41 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const cricket::ContentInfo* video = cricket::GetFirstVideoContent(&desc);
|
|
|
|
|
if (video && !video->rejected &&
|
2020-10-19 13:28:05 +00:00
|
|
|
!rtp_manager()->GetVideoTransceiver()->internal()->channel()) {
|
2022-05-04 10:32:30 +00:00
|
|
|
auto error =
|
|
|
|
|
rtp_manager()->GetVideoTransceiver()->internal()->CreateChannel(
|
|
|
|
|
video->name, pc_->call_ptr(), pc_->configuration()->media_config,
|
|
|
|
|
pc_->SrtpRequired(), pc_->GetCryptoOptions(),
|
|
|
|
|
|
|
|
|
|
audio_options(), video_options(),
|
|
|
|
|
video_bitrate_allocator_factory_.get(), [&](absl::string_view mid) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(network_thread());
|
|
|
|
|
return transport_controller_n()->GetRtpTransport(mid);
|
|
|
|
|
});
|
|
|
|
|
if (!error.ok()) {
|
|
|
|
|
return error;
|
2020-10-05 13:08:41 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const cricket::ContentInfo* data = cricket::GetFirstDataContent(&desc);
|
2023-10-21 16:47:56 +02:00
|
|
|
if (data && !data->rejected && !pc_->CreateDataChannelTransport(data->name)) {
|
2023-07-18 11:03:39 +02:00
|
|
|
LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR,
|
|
|
|
|
"Failed to create data channel.");
|
2020-10-05 13:08:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return RTCError::OK();
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-21 16:50:09 +02:00
|
|
|
void SdpOfferAnswerHandler::DestroyMediaChannels() {
|
2020-10-05 13:08:41 +00:00
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
2020-10-19 13:28:05 +00:00
|
|
|
if (!transceivers()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
Add utility to count the number of blocking thread invokes.
This is useful to understand how often we block in certain parts of the
api and track improvements/regressions.
There are two macros, both are only active for RTC_DCHECK_IS_ON builds:
* RTC_LOG_THREAD_BLOCK_COUNT()
Example:
void MyClass::MyFunction() {
RTC_LOG_THREAD_BLOCK_COUNT();
thread_->Invoke<void>([this](){ DoStuff(); });
}
When executing this function during a test, the output could be:
(my_file.cc:2): Blocking MyFunction: total=1 (actual=1, would=0)
The words 'actual' and 'would' reflect whether an actual thread switch
was made, or if in the case of a test using the same thread for more
than one role (e.g. signaling, worker, network are all the same thread)
that an actual thread switch did not occur but it would have occurred
in the case of having dedicated threads. The 'total' count is the sum.
* RTC_DCHECK_BLOCK_COUNT_NO_MORE_THAN(x)
Example:
void MyClass::MyFunction() {
RTC_LOG_THREAD_BLOCK_COUNT();
thread_->Invoke<void>([this](){ DoStuff(); });
thread_->Invoke<void>([this](){ MoreStuff(); });
RTC_DCHECK_BLOCK_COUNT_NO_MORE_THAN(1);
}
When a function is known to have blocking calls and we want to not
regress from the currently known number of blocking calls, we can use
this macro to state that at a certain point in a function, below
where RTC_LOG_THREAD_BLOCK_COUNT() is called, there must have occurred
no more than |x| (total) blocking calls. If more occur, a DCHECK will
hit and print out what the actual number of calls was:
# Fatal error in: my_file.cc, line 5
# last system error: 60
# Check failed: blocked_call_count_printer.GetTotalBlockedCallCount() <= 1 (2 vs. 1)
Bug: webrtc:12649
Change-Id: Ibac4f85f00b89680601dba54a651eac95a0f45d3
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/213782
Commit-Queue: Tommi <tommi@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#33632}
2021-04-07 10:08:28 +02:00
|
|
|
|
|
|
|
|
RTC_LOG_THREAD_BLOCK_COUNT();
|
|
|
|
|
|
2020-10-05 13:08:41 +00:00
|
|
|
// Destroy video channels first since they may have a pointer to a voice
|
|
|
|
|
// channel.
|
Add utility to count the number of blocking thread invokes.
This is useful to understand how often we block in certain parts of the
api and track improvements/regressions.
There are two macros, both are only active for RTC_DCHECK_IS_ON builds:
* RTC_LOG_THREAD_BLOCK_COUNT()
Example:
void MyClass::MyFunction() {
RTC_LOG_THREAD_BLOCK_COUNT();
thread_->Invoke<void>([this](){ DoStuff(); });
}
When executing this function during a test, the output could be:
(my_file.cc:2): Blocking MyFunction: total=1 (actual=1, would=0)
The words 'actual' and 'would' reflect whether an actual thread switch
was made, or if in the case of a test using the same thread for more
than one role (e.g. signaling, worker, network are all the same thread)
that an actual thread switch did not occur but it would have occurred
in the case of having dedicated threads. The 'total' count is the sum.
* RTC_DCHECK_BLOCK_COUNT_NO_MORE_THAN(x)
Example:
void MyClass::MyFunction() {
RTC_LOG_THREAD_BLOCK_COUNT();
thread_->Invoke<void>([this](){ DoStuff(); });
thread_->Invoke<void>([this](){ MoreStuff(); });
RTC_DCHECK_BLOCK_COUNT_NO_MORE_THAN(1);
}
When a function is known to have blocking calls and we want to not
regress from the currently known number of blocking calls, we can use
this macro to state that at a certain point in a function, below
where RTC_LOG_THREAD_BLOCK_COUNT() is called, there must have occurred
no more than |x| (total) blocking calls. If more occur, a DCHECK will
hit and print out what the actual number of calls was:
# Fatal error in: my_file.cc, line 5
# last system error: 60
# Check failed: blocked_call_count_printer.GetTotalBlockedCallCount() <= 1 (2 vs. 1)
Bug: webrtc:12649
Change-Id: Ibac4f85f00b89680601dba54a651eac95a0f45d3
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/213782
Commit-Queue: Tommi <tommi@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#33632}
2021-04-07 10:08:28 +02:00
|
|
|
auto list = transceivers()->List();
|
|
|
|
|
RTC_DCHECK_BLOCK_COUNT_NO_MORE_THAN(0);
|
|
|
|
|
|
|
|
|
|
for (const auto& transceiver : list) {
|
2020-10-05 13:08:41 +00:00
|
|
|
if (transceiver->media_type() == cricket::MEDIA_TYPE_VIDEO) {
|
2022-04-28 13:31:17 +00:00
|
|
|
transceiver->internal()->ClearChannel();
|
2020-10-05 13:08:41 +00:00
|
|
|
}
|
|
|
|
|
}
|
Add utility to count the number of blocking thread invokes.
This is useful to understand how often we block in certain parts of the
api and track improvements/regressions.
There are two macros, both are only active for RTC_DCHECK_IS_ON builds:
* RTC_LOG_THREAD_BLOCK_COUNT()
Example:
void MyClass::MyFunction() {
RTC_LOG_THREAD_BLOCK_COUNT();
thread_->Invoke<void>([this](){ DoStuff(); });
}
When executing this function during a test, the output could be:
(my_file.cc:2): Blocking MyFunction: total=1 (actual=1, would=0)
The words 'actual' and 'would' reflect whether an actual thread switch
was made, or if in the case of a test using the same thread for more
than one role (e.g. signaling, worker, network are all the same thread)
that an actual thread switch did not occur but it would have occurred
in the case of having dedicated threads. The 'total' count is the sum.
* RTC_DCHECK_BLOCK_COUNT_NO_MORE_THAN(x)
Example:
void MyClass::MyFunction() {
RTC_LOG_THREAD_BLOCK_COUNT();
thread_->Invoke<void>([this](){ DoStuff(); });
thread_->Invoke<void>([this](){ MoreStuff(); });
RTC_DCHECK_BLOCK_COUNT_NO_MORE_THAN(1);
}
When a function is known to have blocking calls and we want to not
regress from the currently known number of blocking calls, we can use
this macro to state that at a certain point in a function, below
where RTC_LOG_THREAD_BLOCK_COUNT() is called, there must have occurred
no more than |x| (total) blocking calls. If more occur, a DCHECK will
hit and print out what the actual number of calls was:
# Fatal error in: my_file.cc, line 5
# last system error: 60
# Check failed: blocked_call_count_printer.GetTotalBlockedCallCount() <= 1 (2 vs. 1)
Bug: webrtc:12649
Change-Id: Ibac4f85f00b89680601dba54a651eac95a0f45d3
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/213782
Commit-Queue: Tommi <tommi@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#33632}
2021-04-07 10:08:28 +02:00
|
|
|
for (const auto& transceiver : list) {
|
2020-10-05 13:08:41 +00:00
|
|
|
if (transceiver->media_type() == cricket::MEDIA_TYPE_AUDIO) {
|
2022-04-28 13:31:17 +00:00
|
|
|
transceiver->internal()->ClearChannel();
|
2020-10-05 13:08:41 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SdpOfferAnswerHandler::GenerateMediaDescriptionOptions(
|
|
|
|
|
const SessionDescriptionInterface* session_desc,
|
|
|
|
|
RtpTransceiverDirection audio_direction,
|
|
|
|
|
RtpTransceiverDirection video_direction,
|
2024-08-29 13:00:40 +00:00
|
|
|
std::optional<size_t>* audio_index,
|
|
|
|
|
std::optional<size_t>* video_index,
|
|
|
|
|
std::optional<size_t>* data_index,
|
2020-10-05 13:08:41 +00:00
|
|
|
cricket::MediaSessionOptions* session_options) {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
for (const cricket::ContentInfo& content :
|
|
|
|
|
session_desc->description()->contents()) {
|
|
|
|
|
if (IsAudioContent(&content)) {
|
|
|
|
|
// If we already have an audio m= section, reject this extra one.
|
|
|
|
|
if (*audio_index) {
|
|
|
|
|
session_options->media_description_options.push_back(
|
|
|
|
|
cricket::MediaDescriptionOptions(
|
|
|
|
|
cricket::MEDIA_TYPE_AUDIO, content.name,
|
|
|
|
|
RtpTransceiverDirection::kInactive, /*stopped=*/true));
|
|
|
|
|
} else {
|
|
|
|
|
bool stopped = (audio_direction == RtpTransceiverDirection::kInactive);
|
|
|
|
|
session_options->media_description_options.push_back(
|
|
|
|
|
cricket::MediaDescriptionOptions(cricket::MEDIA_TYPE_AUDIO,
|
|
|
|
|
content.name, audio_direction,
|
|
|
|
|
stopped));
|
|
|
|
|
*audio_index = session_options->media_description_options.size() - 1;
|
|
|
|
|
}
|
|
|
|
|
session_options->media_description_options.back().header_extensions =
|
2022-05-16 10:36:43 +00:00
|
|
|
media_engine()->voice().GetRtpHeaderExtensions();
|
2020-10-05 13:08:41 +00:00
|
|
|
} else if (IsVideoContent(&content)) {
|
|
|
|
|
// If we already have an video m= section, reject this extra one.
|
|
|
|
|
if (*video_index) {
|
|
|
|
|
session_options->media_description_options.push_back(
|
|
|
|
|
cricket::MediaDescriptionOptions(
|
|
|
|
|
cricket::MEDIA_TYPE_VIDEO, content.name,
|
|
|
|
|
RtpTransceiverDirection::kInactive, /*stopped=*/true));
|
|
|
|
|
} else {
|
|
|
|
|
bool stopped = (video_direction == RtpTransceiverDirection::kInactive);
|
|
|
|
|
session_options->media_description_options.push_back(
|
|
|
|
|
cricket::MediaDescriptionOptions(cricket::MEDIA_TYPE_VIDEO,
|
|
|
|
|
content.name, video_direction,
|
|
|
|
|
stopped));
|
|
|
|
|
*video_index = session_options->media_description_options.size() - 1;
|
|
|
|
|
}
|
|
|
|
|
session_options->media_description_options.back().header_extensions =
|
2022-05-16 10:36:43 +00:00
|
|
|
media_engine()->video().GetRtpHeaderExtensions();
|
2020-10-13 12:43:15 +02:00
|
|
|
} else if (IsUnsupportedContent(&content)) {
|
|
|
|
|
session_options->media_description_options.push_back(
|
|
|
|
|
cricket::MediaDescriptionOptions(cricket::MEDIA_TYPE_UNSUPPORTED,
|
|
|
|
|
content.name,
|
|
|
|
|
RtpTransceiverDirection::kInactive,
|
|
|
|
|
/*stopped=*/true));
|
2020-10-05 13:08:41 +00:00
|
|
|
} else {
|
|
|
|
|
RTC_DCHECK(IsDataContent(&content));
|
|
|
|
|
// If we already have an data m= section, reject this extra one.
|
|
|
|
|
if (*data_index) {
|
|
|
|
|
session_options->media_description_options.push_back(
|
|
|
|
|
GetMediaDescriptionOptionsForRejectedData(content.name));
|
|
|
|
|
} else {
|
|
|
|
|
session_options->media_description_options.push_back(
|
|
|
|
|
GetMediaDescriptionOptionsForActiveData(content.name));
|
|
|
|
|
*data_index = session_options->media_description_options.size() - 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cricket::MediaDescriptionOptions
|
|
|
|
|
SdpOfferAnswerHandler::GetMediaDescriptionOptionsForActiveData(
|
|
|
|
|
const std::string& mid) const {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
// Direction for data sections is meaningless, but legacy endpoints might
|
|
|
|
|
// expect sendrecv.
|
|
|
|
|
cricket::MediaDescriptionOptions options(cricket::MEDIA_TYPE_DATA, mid,
|
|
|
|
|
RtpTransceiverDirection::kSendRecv,
|
|
|
|
|
/*stopped=*/false);
|
|
|
|
|
return options;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cricket::MediaDescriptionOptions
|
|
|
|
|
SdpOfferAnswerHandler::GetMediaDescriptionOptionsForRejectedData(
|
|
|
|
|
const std::string& mid) const {
|
|
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
cricket::MediaDescriptionOptions options(cricket::MEDIA_TYPE_DATA, mid,
|
|
|
|
|
RtpTransceiverDirection::kInactive,
|
|
|
|
|
/*stopped=*/true);
|
|
|
|
|
return options;
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-25 13:44:55 -08:00
|
|
|
bool SdpOfferAnswerHandler::UpdatePayloadTypeDemuxingState(
|
2021-04-26 21:04:26 +02:00
|
|
|
cricket::ContentSource source,
|
|
|
|
|
const std::map<std::string, const cricket::ContentGroup*>&
|
|
|
|
|
bundle_groups_by_mid) {
|
2021-06-07 13:30:46 +02:00
|
|
|
TRACE_EVENT0("webrtc",
|
|
|
|
|
"SdpOfferAnswerHandler::UpdatePayloadTypeDemuxingState");
|
2020-10-05 13:08:41 +00:00
|
|
|
RTC_DCHECK_RUN_ON(signaling_thread());
|
|
|
|
|
// We may need to delete any created default streams and disable creation of
|
|
|
|
|
// new ones on the basis of payload type. This is needed to avoid SSRC
|
|
|
|
|
// collisions in Call's RtpDemuxer, in the case that a transceiver has
|
|
|
|
|
// created a default stream, and then some other channel gets the SSRC
|
2020-10-15 18:22:57 -07:00
|
|
|
// signaled in the corresponding Unified Plan "m=" section. Specifically, we
|
|
|
|
|
// need to disable payload type based demuxing when two bundled "m=" sections
|
|
|
|
|
// are using the same payload type(s). For more context
|
2020-10-05 13:08:41 +00:00
|
|
|
// see https://bugs.chromium.org/p/webrtc/issues/detail?id=11477
|
|
|
|
|
const SessionDescriptionInterface* sdesc =
|
|
|
|
|
(source == cricket::CS_LOCAL ? local_description()
|
|
|
|
|
: remote_description());
|
2021-04-26 21:04:26 +02:00
|
|
|
struct PayloadTypes {
|
|
|
|
|
std::set<int> audio_payload_types;
|
|
|
|
|
std::set<int> video_payload_types;
|
2021-06-09 10:29:50 +02:00
|
|
|
bool pt_demuxing_possible_audio = true;
|
|
|
|
|
bool pt_demuxing_possible_video = true;
|
2021-04-26 21:04:26 +02:00
|
|
|
};
|
|
|
|
|
std::map<const cricket::ContentGroup*, PayloadTypes> payload_types_by_bundle;
|
2021-06-09 10:29:50 +02:00
|
|
|
// If the MID is missing from *any* receiving m= section, this is set to true.
|
|
|
|
|
bool mid_header_extension_missing_audio = false;
|
|
|
|
|
bool mid_header_extension_missing_video = false;
|
2020-10-05 13:08:41 +00:00
|
|
|
for (auto& content_info : sdesc->description()->contents()) {
|
2021-04-26 21:04:26 +02:00
|
|
|
auto it = bundle_groups_by_mid.find(content_info.name);
|
|
|
|
|
const cricket::ContentGroup* bundle_group =
|
|
|
|
|
it != bundle_groups_by_mid.end() ? it->second : nullptr;
|
2020-10-15 18:22:57 -07:00
|
|
|
// If this m= section isn't bundled, it's safe to demux by payload type
|
|
|
|
|
// since other m= sections using the same payload type will also be using
|
|
|
|
|
// different transports.
|
2021-04-26 21:04:26 +02:00
|
|
|
if (!bundle_group) {
|
2020-10-15 18:22:57 -07:00
|
|
|
continue;
|
|
|
|
|
}
|
2021-04-26 21:04:26 +02:00
|
|
|
PayloadTypes* payload_types = &payload_types_by_bundle[bundle_group];
|
2020-10-05 13:08:41 +00:00
|
|
|
if (content_info.rejected ||
|
|
|
|
|
(source == cricket::ContentSource::CS_LOCAL &&
|
|
|
|
|
!RtpTransceiverDirectionHasRecv(
|
|
|
|
|
content_info.media_description()->direction())) ||
|
|
|
|
|
(source == cricket::ContentSource::CS_REMOTE &&
|
|
|
|
|
!RtpTransceiverDirectionHasSend(
|
|
|
|
|
content_info.media_description()->direction()))) {
|
|
|
|
|
// Ignore transceivers that are not receiving.
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2023-11-23 20:21:05 +01:00
|
|
|
const cricket::MediaType media_type =
|
|
|
|
|
content_info.media_description()->type();
|
|
|
|
|
if (media_type == cricket::MediaType::MEDIA_TYPE_AUDIO ||
|
|
|
|
|
media_type == cricket::MediaType::MEDIA_TYPE_VIDEO) {
|
|
|
|
|
if (media_type == cricket::MediaType::MEDIA_TYPE_AUDIO &&
|
|
|
|
|
!mid_header_extension_missing_audio) {
|
|
|
|
|
mid_header_extension_missing_audio =
|
|
|
|
|
!ContentHasHeaderExtension(content_info, RtpExtension::kMidUri);
|
|
|
|
|
} else if (media_type == cricket::MEDIA_TYPE_VIDEO &&
|
|
|
|
|
!mid_header_extension_missing_video) {
|
|
|
|
|
mid_header_extension_missing_video =
|
|
|
|
|
!ContentHasHeaderExtension(content_info, RtpExtension::kMidUri);
|
|
|
|
|
}
|
|
|
|
|
const cricket::MediaContentDescription* media_desc =
|
|
|
|
|
content_info.media_description();
|
|
|
|
|
for (const cricket::Codec& codec : media_desc->codecs()) {
|
|
|
|
|
if (media_type == cricket::MediaType::MEDIA_TYPE_AUDIO) {
|
|
|
|
|
if (payload_types->audio_payload_types.count(codec.id)) {
|
2020-10-15 18:22:57 -07:00
|
|
|
// Two m= sections are using the same payload type, thus demuxing
|
|
|
|
|
// by payload type is not possible.
|
2023-11-23 20:21:05 +01:00
|
|
|
if (media_type == cricket::MediaType::MEDIA_TYPE_AUDIO) {
|
|
|
|
|
payload_types->pt_demuxing_possible_audio = false;
|
|
|
|
|
}
|
2020-10-15 18:22:57 -07:00
|
|
|
}
|
2023-11-23 20:21:05 +01:00
|
|
|
payload_types->audio_payload_types.insert(codec.id);
|
|
|
|
|
} else if (media_type == cricket::MEDIA_TYPE_VIDEO) {
|
|
|
|
|
if (payload_types->video_payload_types.count(codec.id)) {
|
2020-10-15 18:22:57 -07:00
|
|
|
// Two m= sections are using the same payload type, thus demuxing
|
|
|
|
|
// by payload type is not possible.
|
2021-06-09 10:29:50 +02:00
|
|
|
payload_types->pt_demuxing_possible_video = false;
|
2020-10-15 18:22:57 -07:00
|
|
|
}
|
2023-11-23 20:21:05 +01:00
|
|
|
payload_types->video_payload_types.insert(codec.id);
|
2020-10-15 18:22:57 -07:00
|
|
|
}
|
|
|
|
|
}
|
2020-10-05 13:08:41 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-09 10:29:50 +02:00
|
|
|
// In Unified Plan, payload type demuxing is useful for legacy endpoints that
|
|
|
|
|
// don't support the MID header extension, but it can also cause incorrrect
|
|
|
|
|
// forwarding of packets when going from one m= section to multiple m=
|
|
|
|
|
// sections in the same BUNDLE. This only happens if media arrives prior to
|
|
|
|
|
// negotiation, but this can cause missing video and unsignalled ssrc bugs
|
|
|
|
|
// severe enough to warrant disabling PT demuxing in such cases. Therefore, if
|
|
|
|
|
// a MID header extension is present on all m= sections for a given kind
|
|
|
|
|
// (audio/video) then we use that as an OK to disable payload type demuxing in
|
|
|
|
|
// BUNDLEs of that kind. However if PT demuxing was ever turned on (e.g. MID
|
|
|
|
|
// was ever removed on ANY m= section of that kind) then we continue to allow
|
|
|
|
|
// PT demuxing in order to prevent disabling it in follow-up O/A exchanges and
|
|
|
|
|
// allowing early media by PT.
|
|
|
|
|
bool bundled_pt_demux_allowed_audio = !IsUnifiedPlan() ||
|
|
|
|
|
mid_header_extension_missing_audio ||
|
|
|
|
|
pt_demuxing_has_been_used_audio_;
|
|
|
|
|
bool bundled_pt_demux_allowed_video = !IsUnifiedPlan() ||
|
|
|
|
|
mid_header_extension_missing_video ||
|
|
|
|
|
pt_demuxing_has_been_used_video_;
|
|
|
|
|
|
2022-01-02 20:40:22 +00:00
|
|
|
// Gather all updates ahead of time so that all channels can be updated in a
|
2022-09-08 18:38:10 +02:00
|
|
|
// single BlockingCall; necessary due to thread guards.
|
2022-01-02 20:40:22 +00:00
|
|
|
std::vector<std::pair<bool, cricket::ChannelInterface*>> channels_to_update;
|
|
|
|
|
for (const auto& transceiver : transceivers()->ListInternal()) {
|
|
|
|
|
cricket::ChannelInterface* channel = transceiver->channel();
|
|
|
|
|
const ContentInfo* content =
|
|
|
|
|
FindMediaSectionForTransceiver(transceiver, sdesc);
|
|
|
|
|
if (!channel || !content) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const cricket::MediaType media_type = channel->media_type();
|
|
|
|
|
if (media_type != cricket::MediaType::MEDIA_TYPE_AUDIO &&
|
|
|
|
|
media_type != cricket::MediaType::MEDIA_TYPE_VIDEO) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RtpTransceiverDirection local_direction =
|
|
|
|
|
content->media_description()->direction();
|
|
|
|
|
if (source == cricket::CS_REMOTE) {
|
|
|
|
|
local_direction = RtpTransceiverDirectionReversed(local_direction);
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-24 08:45:26 +01:00
|
|
|
auto bundle_it = bundle_groups_by_mid.find(channel->mid());
|
2022-01-02 20:40:22 +00:00
|
|
|
const cricket::ContentGroup* bundle_group =
|
|
|
|
|
bundle_it != bundle_groups_by_mid.end() ? bundle_it->second : nullptr;
|
|
|
|
|
bool pt_demux_enabled = RtpTransceiverDirectionHasRecv(local_direction);
|
|
|
|
|
if (media_type == cricket::MediaType::MEDIA_TYPE_AUDIO) {
|
|
|
|
|
pt_demux_enabled &=
|
|
|
|
|
!bundle_group ||
|
|
|
|
|
(bundled_pt_demux_allowed_audio &&
|
|
|
|
|
payload_types_by_bundle[bundle_group].pt_demuxing_possible_audio);
|
|
|
|
|
if (pt_demux_enabled) {
|
|
|
|
|
pt_demuxing_has_been_used_audio_ = true;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
RTC_DCHECK_EQ(media_type, cricket::MediaType::MEDIA_TYPE_VIDEO);
|
|
|
|
|
pt_demux_enabled &=
|
|
|
|
|
!bundle_group ||
|
|
|
|
|
(bundled_pt_demux_allowed_video &&
|
|
|
|
|
payload_types_by_bundle[bundle_group].pt_demuxing_possible_video);
|
|
|
|
|
if (pt_demux_enabled) {
|
|
|
|
|
pt_demuxing_has_been_used_video_ = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
channels_to_update.emplace_back(pt_demux_enabled, transceiver->channel());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (channels_to_update.empty()) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-08 18:38:10 +02:00
|
|
|
// TODO(bugs.webrtc.org/11993): This BlockingCall() will also block on the
|
|
|
|
|
// network thread for every demuxer sink that needs to be updated. The demuxer
|
|
|
|
|
// state needs to be fully (and only) managed on the network thread and once
|
|
|
|
|
// that's the case, there's no need to stop by on the worker. Ideally we could
|
|
|
|
|
// also do this without blocking.
|
|
|
|
|
return context_->worker_thread()->BlockingCall([&channels_to_update]() {
|
|
|
|
|
for (const auto& it : channels_to_update) {
|
|
|
|
|
if (!it.second->SetPayloadTypeDemuxingEnabled(it.first)) {
|
|
|
|
|
// Note that the state has already been irrevocably changed at this
|
|
|
|
|
// point. Is it useful to stop the loop?
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
});
|
2020-10-05 13:08:41 +00:00
|
|
|
}
|
|
|
|
|
|
2022-05-23 14:57:47 +00:00
|
|
|
bool SdpOfferAnswerHandler::ConfiguredForMedia() const {
|
|
|
|
|
return context_->media_engine();
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-28 13:02:07 +00:00
|
|
|
} // namespace webrtc
|