/* * Copyright 2012 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.
*/
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;
if (local.is_local()) { if (remote.is_stun()) return kIceCandidatePairHostSrflx; if (remote.is_relay()) return kIceCandidatePairHostRelay; if (remote.is_prflx()) return kIceCandidatePairHostPrflx;
} elseif (local.is_stun()) { if (remote.is_local()) return kIceCandidatePairSrflxHost; if (remote.is_stun()) return kIceCandidatePairSrflxSrflx; if (remote.is_relay()) return kIceCandidatePairSrflxRelay; if (remote.is_prflx()) return kIceCandidatePairSrflxPrflx;
} elseif (local.is_relay()) { if (remote.is_local()) return kIceCandidatePairRelayHost; if (remote.is_stun()) return kIceCandidatePairRelaySrflx; if (remote.is_relay()) return kIceCandidatePairRelayRelay; if (remote.is_prflx()) return kIceCandidatePairRelayPrflx;
} elseif (local.is_prflx()) { if (remote.is_local()) return kIceCandidatePairPrflxHost; if (remote.is_stun()) return kIceCandidatePairPrflxSrflx; if (remote.is_relay()) return kIceCandidatePairPrflxRelay;
}
return kIceCandidatePairMax;
}
std::optional<int> RTCConfigurationToIceConfigOptionalInt( int rtc_configuration_parameter) { if (rtc_configuration_parameter ==
PeerConnectionInterface::RTCConfiguration::kUndefined) { return std::nullopt;
} return rtc_configuration_parameter;
}
// Check if the changes of IceTransportsType motives an ice restart. bool NeedIceRestart(bool surface_ice_candidates_on_ice_transport_type_changed,
PeerConnectionInterface::IceTransportsType current,
PeerConnectionInterface::IceTransportsType modified) { if (current == modified) { returnfalse;
}
if (!surface_ice_candidates_on_ice_transport_type_changed) { returntrue;
}
auto current_filter = ConvertIceTransportTypeToCandidateFilter(current); auto modified_filter = ConvertIceTransportTypeToCandidateFilter(modified);
// If surface_ice_candidates_on_ice_transport_type_changed is true and we // extend the filter, then no ice restart is needed. return (current_filter & modified_filter) != current_filter;
}
// Ensures the configuration doesn't have any parameters with invalid values, // or values that conflict with other parameters. // // Returns RTCError::OK() if there are no issues.
RTCError ValidateConfiguration( const PeerConnectionInterface::RTCConfiguration& config) { return cricket::P2PTransportChannel::ValidateIceConfig(
ParseIceConfig(config));
}
// Checks for valid pool size range and if a previous value has already been // set, which is done via SetLocalDescription.
RTCError ValidateIceCandidatePoolSize( int ice_candidate_pool_size,
std::optional<int> previous_ice_candidate_pool_size) { // Note that this isn't possible through chromium, since it's an unsigned // short in WebIDL. if (ice_candidate_pool_size < 0 ||
ice_candidate_pool_size > static_cast<int>(UINT16_MAX)) { return RTCError(RTCErrorType::INVALID_RANGE);
}
// According to JSEP, after setLocalDescription, changing the candidate pool // size is not allowed, and changing the set of ICE servers will not result // in new candidates being gathered. if (previous_ice_candidate_pool_size.has_value() &&
ice_candidate_pool_size != previous_ice_candidate_pool_size.value()) {
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_MODIFICATION, "Can't change candidate pool size after calling " "SetLocalDescription.");
}
return RTCError::OK();
}
// The simplest (and most future-compatible) way to tell if a config was // modified in an invalid way is to copy each property we do support modifying, // then use operator==. There are far more properties we don't support modifying // than those we do, and more could be added. // This helper function accepts a proposed new `configuration` object, an // existing configuration and returns a valid, modified, configuration that's // based on the existing configuration, with modified properties copied from // `configuration`. // If the result of creating a modified configuration doesn't pass the above // `operator==` test or a call to `ValidateConfiguration()`, then the function // will return an error. Otherwise, the return value will be the new config.
RTCErrorOr<PeerConnectionInterface::RTCConfiguration> ApplyConfiguration( const PeerConnectionInterface::RTCConfiguration& configuration, const PeerConnectionInterface::RTCConfiguration& existing_configuration) {
PeerConnectionInterface::RTCConfiguration modified_config =
existing_configuration;
modified_config.servers = configuration.servers;
modified_config.type = configuration.type;
modified_config.ice_candidate_pool_size =
configuration.ice_candidate_pool_size;
modified_config.prune_turn_ports = configuration.prune_turn_ports;
modified_config.turn_port_prune_policy = configuration.turn_port_prune_policy;
modified_config.surface_ice_candidates_on_ice_transport_type_changed =
configuration.surface_ice_candidates_on_ice_transport_type_changed;
modified_config.ice_check_min_interval = configuration.ice_check_min_interval;
modified_config.ice_check_interval_strong_connectivity =
configuration.ice_check_interval_strong_connectivity;
modified_config.ice_check_interval_weak_connectivity =
configuration.ice_check_interval_weak_connectivity;
modified_config.ice_unwritable_timeout = configuration.ice_unwritable_timeout;
modified_config.ice_unwritable_min_checks =
configuration.ice_unwritable_min_checks;
modified_config.ice_inactive_timeout = configuration.ice_inactive_timeout;
modified_config.stun_candidate_keepalive_interval =
configuration.stun_candidate_keepalive_interval;
modified_config.turn_customizer = configuration.turn_customizer;
modified_config.network_preference = configuration.network_preference;
modified_config.active_reset_srtp_params =
configuration.active_reset_srtp_params;
modified_config.turn_logging_id = configuration.turn_logging_id;
modified_config.stable_writable_connection_ping_interval_ms =
configuration.stable_writable_connection_ping_interval_ms; if (configuration != modified_config) {
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_MODIFICATION, "Modifying the configuration in an unsupported way.");
}
RTCError err = ValidateConfiguration(modified_config); if (!err.ok()) { return err;
}
// Enable DTLS by default if we have an identity store or a certificate. return (dependencies.cert_generator || !configuration.certificates.empty());
}
// Calls `ParseIceServersOrError` to extract ice server information from the // `configuration` and then validates the extracted configuration. For a // non-empty list of servers, usage gets recorded via `usage_pattern`.
RTCError ParseAndValidateIceServersFromConfiguration( const PeerConnectionInterface::RTCConfiguration& configuration,
cricket::ServerAddresses& stun_servers,
std::vector<cricket::RelayServerConfig>& turn_servers,
UsagePattern& usage_pattern) {
RTC_DCHECK(stun_servers.empty());
RTC_DCHECK(turn_servers.empty());
RTCError err = ParseIceServersOrError(configuration.servers, &stun_servers,
&turn_servers); if (!err.ok()) { return err;
}
// Restrict number of TURN servers. if (turn_servers.size() > cricket::kMaxTurnServers) {
RTC_LOG(LS_WARNING) << "Number of configured TURN servers is "
<< turn_servers.size()
<< " which exceeds the maximum allowed number of "
<< cricket::kMaxTurnServers;
turn_servers.resize(cricket::kMaxTurnServers);
}
// Add the turn logging id to all turn servers for (cricket::RelayServerConfig& turn_server : turn_servers) {
turn_server.turn_logging_id = configuration.turn_logging_id;
}
// Note if STUN or TURN servers were supplied. if (!stun_servers.empty()) {
usage_pattern.NoteUsageEvent(UsageEvent::STUN_SERVER_ADDED);
} if (!turn_servers.empty()) {
usage_pattern.NoteUsageEvent(UsageEvent::TURN_SERVER_ADDED);
} return RTCError::OK();
}
if (!dependencies.allocator) {
RTC_LOG(LS_ERROR)
<< "PeerConnection initialized without a PortAllocator? " "This shouldn't happen if using PeerConnectionFactory."; return RTCError(
RTCErrorType::INVALID_PARAMETER, "Attempt to create a PeerConnection without a PortAllocatorFactory");
}
if (!dependencies.observer) { // TODO(deadbeef): Why do we do this?
RTC_LOG(LS_ERROR) << "PeerConnection initialized without a " "PeerConnectionObserver"; return RTCError(RTCErrorType::INVALID_PARAMETER, "Attempt to create a PeerConnection without an observer");
}
if (!dependencies.async_dns_resolver_factory) {
dependencies.async_dns_resolver_factory =
std::make_unique<BasicAsyncDnsResolverFactory>();
}
// The PeerConnection constructor consumes some, but not all, dependencies. auto pc = rtc::make_ref_counted<PeerConnection>(
env, context, options, is_unified_plan, std::move(call), dependencies,
dtls_enabled);
RTCError init_error = pc->Initialize(configuration, std::move(dependencies)); if (!init_error.ok()) {
RTC_LOG(LS_ERROR) << "PeerConnection initialization failed"; return init_error;
} return pc;
}
PeerConnection::PeerConnection( const Environment& env,
rtc::scoped_refptr<ConnectionContext> context, const PeerConnectionFactoryInterface::Options& options, bool is_unified_plan,
std::unique_ptr<Call> call,
PeerConnectionDependencies& dependencies, bool dtls_enabled)
: env_(env),
context_(context),
options_(options),
observer_(dependencies.observer),
is_unified_plan_(is_unified_plan),
async_dns_resolver_factory_(
std::move(dependencies.async_dns_resolver_factory)),
port_allocator_(std::move(dependencies.allocator)),
ice_transport_factory_(std::move(dependencies.ice_transport_factory)),
tls_cert_verifier_(std::move(dependencies.tls_cert_verifier)),
call_(std::move(call)),
worker_thread_safety_(PendingTaskSafetyFlag::CreateAttachedToTaskQueue( /*alive=*/call_ != nullptr,
worker_thread())),
call_ptr_(call_.get()), // RFC 3264: The numeric value of the session id and version in the // o line MUST be representable with a "64 bit signed integer". // Due to this constraint session id `session_id_` is max limited to // LLONG_MAX.
session_id_(rtc::ToString(rtc::CreateRandomId64() & LLONG_MAX)),
dtls_enabled_(dtls_enabled),
data_channel_controller_(this),
message_handler_(signaling_thread()),
weak_factory_(this) { // Field trials specific to the peerconnection should be owned by the `env`,
RTC_DCHECK(dependencies.trials == nullptr);
}
if (sdp_handler_) {
sdp_handler_->PrepareForShutdown();
}
// In case `Close()` wasn't called, always make sure the controller cancels // potentially pending operations.
data_channel_controller_.PrepareForShutdown();
// Need to stop transceivers before destroying the stats collector because // AudioRtpSender has a reference to the LegacyStatsCollector it will update // when stopping. if (rtp_manager()) { for (constauto& transceiver : rtp_manager()->transceivers()->List()) {
transceiver->StopInternal();
}
}
legacy_stats_.reset(nullptr); if (stats_collector_) {
stats_collector_->WaitForPendingRequest();
stats_collector_ = nullptr;
}
if (sdp_handler_) { // Don't destroy BaseChannels until after stats has been cleaned up so that // the last stats request can still read from the channels.
sdp_handler_->DestroyMediaChannels();
RTC_LOG(LS_INFO) << "Session: " << session_id() << " is destroyed.";
sdp_handler_->ResetSessionDescFactory();
}
// port_allocator_ and transport_controller_ live on the network thread and // should be destroyed there.
transport_controller_copy_ = nullptr;
network_thread()->BlockingCall([this] {
RTC_DCHECK_RUN_ON(network_thread());
TeardownDataChannelTransport_n(RTCError::OK());
transport_controller_.reset();
port_allocator_.reset(); if (network_thread_safety_)
network_thread_safety_->SetNotAlive();
});
sctp_mid_s_.reset();
SetSctpTransportName("");
// call_ must be destroyed on the worker thread.
worker_thread()->BlockingCall([this] {
RTC_DCHECK_RUN_ON(worker_thread());
worker_thread_safety_->SetNotAlive();
call_.reset();
});
rtc::scoped_refptr<StreamCollectionInterface> PeerConnection::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 sdp_handler_->local_streams();
}
rtc::scoped_refptr<StreamCollectionInterface> PeerConnection::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 sdp_handler_->remote_streams();
}
bool PeerConnection::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.";
TRACE_EVENT0("webrtc", "PeerConnection::AddStream"); if (!ConfiguredForMedia()) {
RTC_LOG(LS_ERROR) << "AddStream: Not configured for media"; returnfalse;
} return sdp_handler_->AddStream(local_stream);
}
void PeerConnection::RemoveStream(MediaStreamInterface* local_stream) {
RTC_DCHECK_RUN_ON(signaling_thread());
RTC_DCHECK(ConfiguredForMedia());
RTC_CHECK(!IsUnifiedPlan()) << "RemoveStream is not available with Unified " "Plan SdpSemantics. Please use RemoveTrack " "instead.";
TRACE_EVENT0("webrtc", "PeerConnection::RemoveStream");
sdp_handler_->RemoveStream(local_stream);
}
RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>>
PeerConnection::AddTransceiver(cricket::MediaType media_type, const RtpTransceiverInit& init) {
RTC_DCHECK_RUN_ON(signaling_thread()); if (!ConfiguredForMedia()) {
LOG_AND_RETURN_ERROR(RTCErrorType::UNSUPPORTED_OPERATION, "Not configured for media");
}
RTC_CHECK(IsUnifiedPlan())
<< "AddTransceiver is only available with Unified Plan SdpSemantics"; if (!(media_type == cricket::MEDIA_TYPE_AUDIO ||
media_type == cricket::MEDIA_TYPE_VIDEO)) {
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, "media type is not audio or video");
} return AddTransceiver(media_type, nullptr, init);
}
size_t num_rids = absl::c_count_if(init.send_encodings,
[](const RtpEncodingParameters& encoding) { return !encoding.rid.empty();
}); if (num_rids > 0 && num_rids != init.send_encodings.size()) {
LOG_AND_RETURN_ERROR(
RTCErrorType::INVALID_PARAMETER, "RIDs must be provided for either all or none of the send encodings.");
}
if (num_rids > 0 && absl::c_any_of(init.send_encodings,
[](const RtpEncodingParameters& encoding) { return !IsLegalRsidName(encoding.rid);
})) {
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, "Invalid RID value provided.");
}
if (absl::c_any_of(init.send_encodings,
[](const RtpEncodingParameters& encoding) { return encoding.ssrc.has_value();
})) {
LOG_AND_RETURN_ERROR(
RTCErrorType::UNSUPPORTED_PARAMETER, "Attempted to set an unimplemented parameter of RtpParameters.");
}
// Encodings are dropped from the tail if too many are provided.
size_t max_simulcast_streams =
media_type == cricket::MEDIA_TYPE_VIDEO ? kMaxSimulcastStreams : 1u; if (parameters.encodings.size() > max_simulcast_streams) {
parameters.encodings.erase(
parameters.encodings.begin() + max_simulcast_streams,
parameters.encodings.end());
}
// Single RID should be removed. if (parameters.encodings.size() == 1 &&
!parameters.encodings[0].rid.empty()) {
RTC_LOG(LS_INFO) << "Removing RID: " << parameters.encodings[0].rid << ".";
parameters.encodings[0].rid.clear();
}
// If RIDs were not provided, they are generated for simulcast scenario. if (parameters.encodings.size() > 1 && num_rids == 0) {
rtc::UniqueStringGenerator rid_generator; for (RtpEncodingParameters& encoding : parameters.encodings) {
encoding.rid = rid_generator.GenerateString();
}
}
// If no encoding parameters were provided, a default entry is created. if (parameters.encodings.empty()) {
parameters.encodings.push_back({});
}
if (UnimplementedRtpParameterHasValue(parameters)) {
LOG_AND_RETURN_ERROR(
RTCErrorType::UNSUPPORTED_PARAMETER, "Attempted to set an unimplemented parameter of RtpParameters.");
}
std::vector<cricket::Codec> codecs; // Gather the current codec capabilities to allow checking scalabilityMode and // codec selection against supported values. if (media_type == cricket::MEDIA_TYPE_VIDEO) {
codecs = context_->media_engine()->video().send_codecs(false);
} else {
codecs = context_->media_engine()->voice().send_codecs();
}
auto result = cricket::CheckRtpParametersValues(
parameters, codecs, std::nullopt, env_.field_trials()); if (!result.ok()) { if (result.type() == RTCErrorType::INVALID_MODIFICATION) {
result.set_type(RTCErrorType::UNSUPPORTED_OPERATION);
}
LOG_AND_RETURN_ERROR(result.type(), result.message());
}
RTC_LOG(LS_INFO) << "Adding " << cricket::MediaTypeToString(media_type)
<< " transceiver in response to a call to AddTransceiver."; // Set the sender ID equal to the track ID if the track is specified unless // that sender ID is already in use.
std::string sender_id = (track && !rtp_manager()->FindSenderById(track->id())
? track->id()
: rtc::CreateRandomUuid()); auto sender = rtp_manager()->CreateSender(
media_type, sender_id, track, init.stream_ids, parameters.encodings); auto receiver =
rtp_manager()->CreateReceiver(media_type, rtc::CreateRandomUuid()); auto transceiver = rtp_manager()->CreateAndAddTransceiver(sender, receiver);
transceiver->internal()->set_direction(init.direction);
if (update_negotiation_needed) {
sdp_handler_->UpdateNegotiationNeeded();
}
rtc::scoped_refptr<RtpSenderInterface> PeerConnection::CreateSender( const std::string& kind, const std::string& stream_id) {
RTC_DCHECK_RUN_ON(signaling_thread()); if (!ConfiguredForMedia()) {
RTC_LOG(LS_ERROR) << "Not configured for media"; return nullptr;
}
RTC_CHECK(!IsUnifiedPlan()) << "CreateSender is not available with Unified " "Plan SdpSemantics. Please use AddTransceiver " "instead.";
TRACE_EVENT0("webrtc", "PeerConnection::CreateSender"); if (IsClosed()) { return nullptr;
}
// Internally we need to have one stream with Plan B semantics, so we // generate a random stream ID if not specified.
std::vector<std::string> stream_ids; if (stream_id.empty()) {
stream_ids.push_back(rtc::CreateRandomUuid());
RTC_LOG(LS_INFO)
<< "No stream_id specified for sender. Generated stream ID: "
<< stream_ids[0];
} else {
stream_ids.push_back(stream_id);
}
// TODO(steveanton): Move construction of the RtpSenders to RtpTransceiver.
rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>> new_sender; if (kind == MediaStreamTrackInterface::kAudioKind) { auto audio_sender =
AudioRtpSender::Create(env_, worker_thread(), rtc::CreateRandomUuid(),
legacy_stats_.get(), rtp_manager());
audio_sender->SetMediaChannel(rtp_manager()->voice_media_send_channel());
new_sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
signaling_thread(), audio_sender);
rtp_manager()->GetAudioTransceiver()->internal()->AddSender(new_sender);
} elseif (kind == MediaStreamTrackInterface::kVideoKind) { auto video_sender = VideoRtpSender::Create(
env_, worker_thread(), rtc::CreateRandomUuid(), rtp_manager());
video_sender->SetMediaChannel(rtp_manager()->video_media_send_channel());
new_sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
signaling_thread(), video_sender);
rtp_manager()->GetVideoTransceiver()->internal()->AddSender(new_sender);
} else {
RTC_LOG(LS_ERROR) << "CreateSender called with invalid kind: " << kind; return nullptr;
}
new_sender->internal()->set_stream_ids(stream_ids);
// The LegacyStatsCollector is used to tell if a track is valid because it may // remember tracks that the PeerConnection previously removed. if (track && !legacy_stats_->IsValidTrack(track->id())) {
RTC_LOG(LS_WARNING) << "Legacy GetStats is called with an invalid track: "
<< track->id(); returnfalse;
}
message_handler_.PostGetStats(observer, legacy_stats_.get(), track);
void PeerConnection::GetStats(
rtc::scoped_refptr<RtpSenderInterface> selector,
rtc::scoped_refptr<RTCStatsCollectorCallback> callback) {
TRACE_EVENT0("webrtc", "PeerConnection::GetStats");
RTC_DCHECK_RUN_ON(signaling_thread());
RTC_DCHECK(callback);
RTC_DCHECK(stats_collector_);
RTC_LOG_THREAD_BLOCK_COUNT();
rtc::scoped_refptr<RtpSenderInternal> internal_sender; if (selector) { for (constauto& proxy_transceiver :
rtp_manager()->transceivers()->List()) { for (constauto& proxy_sender :
proxy_transceiver->internal()->senders()) { if (proxy_sender == selector) {
internal_sender = proxy_sender->internal(); break;
}
} if (internal_sender) break;
}
} // If there is no `internal_sender` then `selector` is either null or does not // belong to the PeerConnection (in Plan B, senders can be removed from the // PeerConnection). This means that "all the stats objects representing the // selector" is an empty set. Invoking GetStatsReport() with a null selector // produces an empty stats report.
stats_collector_->GetStatsReport(internal_sender, callback);
RTC_DCHECK_BLOCK_COUNT_NO_MORE_THAN(2);
}
void PeerConnection::GetStats(
rtc::scoped_refptr<RtpReceiverInterface> selector,
rtc::scoped_refptr<RTCStatsCollectorCallback> callback) {
TRACE_EVENT0("webrtc", "PeerConnection::GetStats");
RTC_DCHECK_RUN_ON(signaling_thread());
RTC_DCHECK(callback);
RTC_DCHECK(stats_collector_);
RTC_LOG_THREAD_BLOCK_COUNT();
rtc::scoped_refptr<RtpReceiverInternal> internal_receiver; if (selector) { for (constauto& proxy_transceiver :
rtp_manager()->transceivers()->List()) { for (constauto& proxy_receiver :
proxy_transceiver->internal()->receivers()) { if (proxy_receiver == selector) {
internal_receiver = proxy_receiver->internal(); break;
}
} if (internal_receiver) break;
}
} // If there is no `internal_receiver` then `selector` is either null or does // not belong to the PeerConnection (in Plan B, receivers can be removed from // the PeerConnection). This means that "all the stats objects representing // the selector" is an empty set. Invoking GetStatsReport() with a null // selector produces an empty stats report.
stats_collector_->GetStatsReport(internal_receiver, callback);
RTC_DCHECK_BLOCK_COUNT_NO_MORE_THAN(2);
}
if (has_local_description &&
configuration.crypto_options != configuration_.crypto_options) {
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_MODIFICATION, "Can't change crypto_options after calling " "SetLocalDescription.");
}
// Create a new, configuration object whose Ice config will have been // validated..
RTCErrorOr<RTCConfiguration> validated_config =
ApplyConfiguration(configuration, configuration_); if (!validated_config.ok()) { return validated_config.error();
}
// Parse ICE servers before hopping to network thread.
cricket::ServerAddresses stun_servers;
std::vector<cricket::RelayServerConfig> turn_servers;
validate_error = ParseAndValidateIceServersFromConfiguration(
configuration, stun_servers, turn_servers, usage_pattern_); if (!validate_error.ok()) { return validate_error;
}
// Apply part of the configuration on the network thread. In theory this // shouldn't fail. if (!network_thread()->BlockingCall(
[this, needs_ice_restart, &ice_config, &stun_servers, &turn_servers,
&modified_config, has_local_description] {
RTC_DCHECK_RUN_ON(network_thread()); // As described in JSEP, calling setConfiguration with new ICE // servers or candidate policy must set a "needs-ice-restart" bit so // that the next offer triggers an ICE restart which will pick up // the changes. if (needs_ice_restart)
transport_controller_->SetNeedsIceRestartFlag();
rtc::scoped_refptr<DtlsTransport>
PeerConnection::LookupDtlsTransportByMidInternal(const std::string& mid) {
RTC_DCHECK_RUN_ON(signaling_thread()); // TODO(bugs.webrtc.org/9987): Avoid the thread jump. // This might be done by caching the value on the signaling thread. return network_thread()->BlockingCall([this, mid]() {
RTC_DCHECK_RUN_ON(network_thread()); return transport_controller_->LookupDtlsTransportByMid(mid);
});
}
rtc::scoped_refptr<SctpTransportInterface> PeerConnection::GetSctpTransport() const {
RTC_DCHECK_RUN_ON(network_thread()); if (!sctp_mid_n_) return nullptr;
if (IsClosed()) { return;
} // Update stats here so that we have the most recent stats for tracks and // streams before the channels are closed.
legacy_stats_->UpdateStats(kStatsOutputLevelStandard);
if (ConfiguredForMedia()) { for (constauto& transceiver : rtp_manager()->transceivers()->List()) {
transceiver->internal()->SetPeerConnectionClosed(); if (!transceiver->stopped())
transceiver->StopInternal();
}
} // Ensure that all asynchronous stats requests are completed before destroying // the transport controller below. if (stats_collector_) {
stats_collector_->WaitForPendingRequest();
}
// Don't destroy BaseChannels until after stats has been cleaned up so that // the last stats request can still read from the channels. // TODO(tommi): The voice/video channels will be partially uninitialized on // the network thread (see `RtpTransceiver::ClearChannel`), partially on the // worker thread (see `PushNewMediaChannelAndDeleteChannel`) and then // eventually freed on the signaling thread. // It would be good to combine those steps with the teardown steps here.
sdp_handler_->DestroyMediaChannels();
// The event log is used in the transport controller, which must be outlived // by the former. CreateOffer by the peer connection is implemented // asynchronously and if the peer connection is closed without resetting the // WebRTC session description factory, the session description factory would // call the transport controller.
sdp_handler_->ResetSessionDescFactory(); if (ConfiguredForMedia()) {
rtp_manager_->Close();
}
// Signal shutdown to the sdp handler. This invalidates weak pointers for // internal pending callbacks.
sdp_handler_->PrepareForShutdown();
data_channel_controller_.PrepareForShutdown();
// The .h file says that observer can be discarded after close() returns. // Make sure this is true.
observer_ = nullptr;
}
void PeerConnection::SetConnectionState(
PeerConnectionInterface::PeerConnectionState new_state) { if (connection_state_ == new_state) return; if (IsClosed()) return;
connection_state_ = new_state;
Observer()->OnConnectionChange(new_state);
// The first connection state change to connected happens once per // connection which makes it a good point to report metrics. if (new_state == PeerConnectionState::kConnected && !was_ever_connected_) {
was_ever_connected_ = true;
ReportFirstConnectUsageMetrics();
}
}
void PeerConnection::ReportFirstConnectUsageMetrics() { // Record bundle-policy from configuration. Done here from // connectionStateChange to limit to actually established connections.
BundlePolicyUsage policy = kBundlePolicyUsageMax; switch (configuration_.bundle_policy) { case kBundlePolicyBalanced:
policy = kBundlePolicyUsageBalanced; break; case kBundlePolicyMaxBundle:
policy = kBundlePolicyUsageMaxBundle; break; case kBundlePolicyMaxCompat:
policy = kBundlePolicyUsageMaxCompat; break;
}
RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.BundlePolicy", policy,
kBundlePolicyUsageMax);
// Record whether there was a local or remote provisional answer.
ProvisionalAnswerUsage pranswer = kProvisionalAnswerNotUsed; if (local_description()->GetType() == SdpType::kPrAnswer) {
pranswer = kProvisionalAnswerLocal;
} elseif (remote_description()->GetType() == SdpType::kPrAnswer) {
pranswer = kProvisionalAnswerRemote;
}
RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.ProvisionalAnswer", pranswer,
kProvisionalAnswerMax);
auto transport_infos = remote_description()->description()->transport_infos(); if (!transport_infos.empty()) { // Record the number of valid / invalid ice-ufrag. We do allow certain // non-spec ice-char for backward-compat reasons. At this point we know // that the ufrag/pwd consists of a valid ice-char or one of the four // not allowed characters since we have passed the IsIceChar check done // by the p2p transport description on setRemoteDescription calls. auto ice_parameters = transport_infos[0].description.GetIceParameters(); auto is_invalid_char = [](char c) { return c == '-' || c == '=' || c == '#' || c == '_';
}; bool isUsingInvalidIceCharInUfrag =
absl::c_any_of(ice_parameters.ufrag, is_invalid_char); bool isUsingInvalidIceCharInPwd =
absl::c_any_of(ice_parameters.pwd, is_invalid_char);
RTC_HISTOGRAM_BOOLEAN( "WebRTC.PeerConnection.ValidIceChars",
!(isUsingInvalidIceCharInUfrag || isUsingInvalidIceCharInPwd));
// Record whether the hash algorithm of the first transport's // DTLS fingerprint is still using SHA-1. if (transport_infos[0].description.identity_fingerprint) {
RTC_HISTOGRAM_BOOLEAN( "WebRTC.PeerConnection.DtlsFingerprintLegacySha1",
absl::EqualsIgnoreCase(
transport_infos[0].description.identity_fingerprint->algorithm, "sha-1"));
}
}
// Record RtcpMuxPolicy setting.
RtcpMuxPolicyUsage rtcp_mux_policy = kRtcpMuxPolicyUsageMax; switch (configuration_.rtcp_mux_policy) { case kRtcpMuxPolicyNegotiate:
rtcp_mux_policy = kRtcpMuxPolicyUsageNegotiate; break; case kRtcpMuxPolicyRequire:
rtcp_mux_policy = kRtcpMuxPolicyUsageRequire; break;
}
RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.RtcpMuxPolicy",
rtcp_mux_policy, kRtcpMuxPolicyUsageMax);
}
port_allocator_->Initialize(); // To handle both internal and externally created port allocator, we will // enable BUNDLE here. int port_allocator_flags = port_allocator_->flags();
port_allocator_flags |= cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET |
cricket::PORTALLOCATOR_ENABLE_IPV6 |
cricket::PORTALLOCATOR_ENABLE_IPV6_ON_WIFI; if (trials().IsDisabled("WebRTC-IPv6Default")) {
port_allocator_flags &= ~(cricket::PORTALLOCATOR_ENABLE_IPV6);
} if (configuration.disable_ipv6_on_wifi) {
port_allocator_flags &= ~(cricket::PORTALLOCATOR_ENABLE_IPV6_ON_WIFI);
RTC_LOG(LS_INFO) << "IPv6 candidates on Wi-Fi are disabled.";
}
if (configuration.tcp_candidate_policy == kTcpCandidatePolicyDisabled) {
port_allocator_flags |= cricket::PORTALLOCATOR_DISABLE_TCP;
RTC_LOG(LS_INFO) << "TCP candidates are disabled.";
}
if (configuration.candidate_network_policy ==
kCandidateNetworkPolicyLowCost) {
port_allocator_flags |= cricket::PORTALLOCATOR_DISABLE_COSTLY_NETWORKS;
RTC_LOG(LS_INFO) << "Do not gather candidates on high-cost networks";
}
if (configuration.disable_link_local_networks) {
port_allocator_flags |= cricket::PORTALLOCATOR_DISABLE_LINK_LOCAL_NETWORKS;
RTC_LOG(LS_INFO) << "Disable candidates on link-local network interfaces.";
}
port_allocator_->set_flags(port_allocator_flags); // No step delay is used while allocating ports.
port_allocator_->set_step_delay(cricket::kMinimumStepDelay);
port_allocator_->SetCandidateFilter(
ConvertIceTransportTypeToCandidateFilter(configuration.type));
port_allocator_->set_max_ipv6_networks(configuration.max_ipv6_networks);
auto turn_servers_copy = turn_servers; for (auto& turn_server : turn_servers_copy) {
turn_server.tls_cert_verifier = tls_cert_verifier_.get();
} // Call this last since it may create pooled allocator sessions using the // properties set above.
port_allocator_->SetConfiguration(
stun_servers, std::move(turn_servers_copy),
configuration.ice_candidate_pool_size,
configuration.GetTurnPortPrunePolicy(), configuration.turn_customizer,
configuration.stun_candidate_keepalive_interval);
bool PeerConnection::ReconfigurePortAllocator_n( const cricket::ServerAddresses& stun_servers, const std::vector<cricket::RelayServerConfig>& turn_servers,
IceTransportsType type, int candidate_pool_size,
PortPrunePolicy turn_port_prune_policy,
TurnCustomizer* turn_customizer,
std::optional<int> stun_candidate_keepalive_interval, bool have_local_description) {
RTC_DCHECK_RUN_ON(network_thread());
port_allocator_->SetCandidateFilter(
ConvertIceTransportTypeToCandidateFilter(type)); // Add the custom tls turn servers if they exist. auto turn_servers_copy = turn_servers; for (auto& turn_server : turn_servers_copy) {
turn_server.tls_cert_verifier = tls_cert_verifier_.get();
} // Call this last since it may create pooled allocator sessions using the // candidate filter set above. return port_allocator_->SetConfiguration(
stun_servers, std::move(turn_servers_copy), candidate_pool_size,
turn_port_prune_policy, turn_customizer,
stun_candidate_keepalive_interval);
}
bool PeerConnection::GetSslRole(const std::string& content_name,
rtc::SSLRole* role) {
RTC_DCHECK_RUN_ON(signaling_thread()); if (!local_description() || !remote_description()) {
RTC_LOG(LS_INFO)
<< "Local and Remote descriptions must be applied to get the " "SSL Role of the session."; returnfalse;
}
auto dtls_role = network_thread()->BlockingCall([this, content_name]() {
RTC_DCHECK_RUN_ON(network_thread()); return transport_controller_->GetDtlsRole(content_name);
}); if (dtls_role) {
*role = *dtls_role; returntrue;
} returnfalse;
}
void PeerConnection::OnTransportControllerConnectionState(
cricket::IceConnectionState state) { switch (state) { case cricket::kIceConnectionConnecting: // If the current state is Connected or Completed, then there were // writable channels but now there are not, so the next state must // be Disconnected. // kIceConnectionConnecting is currently used as the default, // un-connected state by the TransportController, so its only use is // detecting disconnections. if (ice_connection_state_ ==
PeerConnectionInterface::kIceConnectionConnected ||
ice_connection_state_ ==
PeerConnectionInterface::kIceConnectionCompleted) {
SetIceConnectionState(
PeerConnectionInterface::kIceConnectionDisconnected);
} break; case cricket::kIceConnectionFailed:
SetIceConnectionState(PeerConnectionInterface::kIceConnectionFailed); break; case cricket::kIceConnectionConnected:
RTC_LOG(LS_INFO) << "Changing to ICE connected state because " "all transports are writable.";
{
std::vector<RtpTransceiverProxyRefPtr> transceivers; if (ConfiguredForMedia()) {
transceivers = rtp_manager()->transceivers()->List();
}
SetIceConnectionState(PeerConnectionInterface::kIceConnectionConnected);
NoteUsageEvent(UsageEvent::ICE_STATE_CONNECTED); break; case cricket::kIceConnectionCompleted:
RTC_LOG(LS_INFO) << "Changing to ICE completed state because " "all transports are complete."; if (ice_connection_state_ !=
PeerConnectionInterface::kIceConnectionConnected) { // If jumping directly from "checking" to "connected", // signal "connected" first.
SetIceConnectionState(PeerConnectionInterface::kIceConnectionConnected);
}
SetIceConnectionState(PeerConnectionInterface::kIceConnectionCompleted);
void PeerConnection::OnTransportControllerCandidatesGathered( const std::string& transport_name, const cricket::Candidates& candidates) { // TODO(bugs.webrtc.org/12427): Expect this to come in on the network thread // (not signaling as it currently does), handle appropriately. int sdp_mline_index; if (!GetLocalCandidateMediaIndex(transport_name, &sdp_mline_index)) {
RTC_LOG(LS_ERROR)
<< "OnTransportControllerCandidatesGathered: content name "
<< transport_name << " not found"; return;
}
for (cricket::Candidates::const_iterator citer = candidates.begin();
citer != candidates.end(); ++citer) { // Use transport_name as the candidate media id.
std::unique_ptr<JsepIceCandidate> candidate( new JsepIceCandidate(transport_name, sdp_mline_index, *citer));
sdp_handler_->AddLocalIceCandidate(candidate.get());
OnIceCandidate(std::move(candidate));
}
}
// Returns the media index for a local ice candidate given the content name. bool PeerConnection::GetLocalCandidateMediaIndex( const std::string& content_name, int* sdp_mline_index) { if (!local_description() || !sdp_mline_index) { returnfalse;
}
bool content_found = false; const ContentInfos& contents = local_description()->description()->contents(); for (size_t index = 0; index < contents.size(); ++index) { if (contents[index].name == content_name) {
*sdp_mline_index = static_cast<int>(index);
content_found = true; break;
}
} return content_found;
}
std::optional<std::string> PeerConnection::SetupDataChannelTransport_n(
absl::string_view mid) {
sctp_mid_n_ = std::string(mid);
DataChannelTransportInterface* transport =
transport_controller_->GetDataChannelTransport(*sctp_mid_n_); if (!transport) {
RTC_LOG(LS_ERROR)
<< "Data channel transport is not available for data channels, mid="
<< mid;
sctp_mid_n_ = std::nullopt; return std::nullopt;
}
std::optional<std::string> transport_name;
cricket::DtlsTransportInternal* dtls_transport =
transport_controller_->GetDtlsTransport(*sctp_mid_n_); if (dtls_transport) {
transport_name = dtls_transport->transport_name();
} else { // Make sure we still set a valid string.
transport_name = std::string("");
}
void PeerConnection::TeardownDataChannelTransport_n(RTCError error) { if (sctp_mid_n_) { // `sctp_mid_` may still be active through an SCTP transport. If not, unset // it.
RTC_LOG(LS_INFO) << "Tearing down data channel transport for mid="
<< *sctp_mid_n_;
sctp_mid_n_.reset();
}
// Returns false if bundle is enabled and rtcp_mux is disabled. bool PeerConnection::ValidateBundleSettings( const SessionDescription* desc, const std::map<std::string, const cricket::ContentGroup*>&
bundle_groups_by_mid) { if (bundle_groups_by_mid.empty()) returntrue;
const cricket::ContentInfos& contents = desc->contents(); for (cricket::ContentInfos::const_iterator citer = contents.begin();
citer != contents.end(); ++citer) { const cricket::ContentInfo* content = (&*citer);
RTC_DCHECK(content != NULL); auto it = bundle_groups_by_mid.find(content->name); if (it != bundle_groups_by_mid.end() &&
!(content->rejected || content->bundle_only) &&
content->type == MediaProtocolType::kRtp) { if (!HasRtcpMuxEnabled(content)) returnfalse;
}
} // RTCP-MUX is enabled in all the contents. returntrue;
}
// Asynchronously adds remote candidates on the network thread. void PeerConnection::AddRemoteCandidate(const std::string& mid, const cricket::Candidate& candidate) {
RTC_DCHECK_RUN_ON(signaling_thread());
if (candidate.network_type() != rtc::ADAPTER_TYPE_UNKNOWN) {
RTC_DLOG(LS_WARNING) << "Using candidate with adapter type set - this " "should only happen in test";
}
// Clear fields that do not make sense as remote candidates.
cricket::Candidate new_candidate(candidate);
new_candidate.set_network_type(rtc::ADAPTER_TYPE_UNKNOWN);
new_candidate.set_relay_protocol("");
new_candidate.set_underlying_type_for_vpn(rtc::ADAPTER_TYPE_UNKNOWN);
network_thread()->PostTask(SafeTask(
network_thread_safety_, [this, mid = mid, candidate = new_candidate] {
RTC_DCHECK_RUN_ON(network_thread());
std::vector<cricket::Candidate> candidates = {candidate};
RTCError error =
transport_controller_->AddRemoteCandidates(mid, candidates); if (error.ok()) {
signaling_thread()->PostTask(SafeTask(
signaling_thread_safety_.flag(),
[this, candidate = std::move(candidate)] {
ReportRemoteIceCandidateAdded(candidate); // Candidates successfully submitted for checking. if (ice_connection_state() ==
PeerConnectionInterface::kIceConnectionNew ||
ice_connection_state() ==
PeerConnectionInterface::kIceConnectionDisconnected) { // If state is New, then the session has just gotten its first // remote ICE candidates, so go to Checking. If state is // Disconnected, the session is re-using old candidates or // receiving additional ones, so go to Checking. If state is // Connected, stay Connected. // TODO(bemasc): If state is Connected, and the new candidates // are for a newly added transport, then the state actually // _should_ move to checking. Add a way to distinguish that // case.
SetIceConnectionState(
PeerConnectionInterface::kIceConnectionChecking);
} // TODO(bemasc): If state is Completed, go back to Connected.
}));
} else {
RTC_LOG(LS_WARNING) << error.message();
}
}));
}
// Walk through the ConnectionInfos to gather best connection usage // for IPv4 and IPv6. // static (no member state required) void PeerConnection::ReportBestConnectionState( const cricket::TransportStats& stats) { for (const cricket::TransportChannelStats& channel_stats :
stats.channel_stats) { for (const cricket::ConnectionInfo& connection_info :
channel_stats.ice_transport_stats.connection_infos) { if (!connection_info.best_connection) { continue;
}
const cricket::Candidate& local = connection_info.local_candidate; const cricket::Candidate& remote = connection_info.remote_candidate;
void PeerConnection::StartSctpTransport(int local_port, int remote_port, int max_message_size) {
RTC_DCHECK_RUN_ON(signaling_thread()); if (!sctp_mid_s_) return;
std::function<void(const RtpPacketReceived& parsed_packet)>
PeerConnection::InitializeUnDemuxablePacketHandler() {
RTC_DCHECK_RUN_ON(network_thread()); return [this](const RtpPacketReceived& parsed_packet) {
worker_thread()->PostTask(
SafeTask(worker_thread_safety_, [this, parsed_packet]() { // Deliver the packet anyway to Call to allow Call to do BWE. // Even if there is no media receiver, the packet has still // been received on the network and has been correcly parsed.
call_ptr_->Receiver()->DeliverRtpPacket(
MediaType::ANY, parsed_packet, /*undemuxable_packet_handler=*/
[](const RtpPacketReceived& packet) { returnfalse; });
}));
};
}
} // namespace webrtc
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.50 Sekunden
(vorverarbeitet am 2026-04-26)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.