| /* |
| * 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. |
| */ |
| |
| #include "pc/peer_connection.h" |
| |
| #include <algorithm> |
| #include <limits> |
| #include <memory> |
| #include <queue> |
| #include <set> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/algorithm/container.h" |
| #include "absl/strings/match.h" |
| #include "api/jsep_ice_candidate.h" |
| #include "api/jsep_session_description.h" |
| #include "api/media_stream_proxy.h" |
| #include "api/media_stream_track_proxy.h" |
| #include "api/rtc_error.h" |
| #include "api/rtc_event_log/rtc_event_log.h" |
| #include "api/rtc_event_log_output_file.h" |
| #include "api/rtp_parameters.h" |
| #include "api/uma_metrics.h" |
| #include "api/video/builtin_video_bitrate_allocator_factory.h" |
| #include "call/call.h" |
| #include "logging/rtc_event_log/ice_logger.h" |
| #include "media/base/rid_description.h" |
| #include "media/sctp/sctp_transport.h" |
| #include "pc/audio_rtp_receiver.h" |
| #include "pc/audio_track.h" |
| #include "pc/channel.h" |
| #include "pc/channel_manager.h" |
| #include "pc/dtmf_sender.h" |
| #include "pc/media_stream.h" |
| #include "pc/media_stream_observer.h" |
| #include "pc/remote_audio_source.h" |
| #include "pc/rtp_media_utils.h" |
| #include "pc/rtp_receiver.h" |
| #include "pc/rtp_sender.h" |
| #include "pc/sctp_transport.h" |
| #include "pc/sctp_utils.h" |
| #include "pc/sdp_utils.h" |
| #include "pc/stream_collection.h" |
| #include "pc/video_rtp_receiver.h" |
| #include "pc/video_track.h" |
| #include "rtc_base/bind.h" |
| #include "rtc_base/checks.h" |
| #include "rtc_base/logging.h" |
| #include "rtc_base/string_encode.h" |
| #include "rtc_base/strings/string_builder.h" |
| #include "rtc_base/trace_event.h" |
| #include "system_wrappers/include/clock.h" |
| #include "system_wrappers/include/field_trial.h" |
| #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; |
| |
| using cricket::LOCAL_PORT_TYPE; |
| using cricket::PRFLX_PORT_TYPE; |
| using cricket::RELAY_PORT_TYPE; |
| using cricket::STUN_PORT_TYPE; |
| |
| namespace webrtc { |
| |
| // Error messages |
| const char kBundleWithoutRtcpMux[] = |
| "rtcp-mux must be enabled when BUNDLE " |
| "is enabled."; |
| const char kInvalidCandidates[] = "Description contains invalid candidates."; |
| const char kInvalidSdp[] = "Invalid session description."; |
| 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 kSdpWithoutDtlsFingerprint[] = |
| "Called with SDP without DTLS fingerprint."; |
| const char kSdpWithoutSdesCrypto[] = "Called with SDP without SDES crypto."; |
| const char kSdpWithoutIceUfragPwd[] = |
| "Called with SDP without ice-ufrag and ice-pwd."; |
| const char kSessionError[] = "Session error code: "; |
| const char kSessionErrorDesc[] = "Session error description: "; |
| const char kDtlsSrtpSetupFailureRtp[] = |
| "Couldn't set up DTLS-SRTP on RTP channel."; |
| const char kDtlsSrtpSetupFailureRtcp[] = |
| "Couldn't set up DTLS-SRTP on RTCP channel."; |
| |
| namespace { |
| |
| // Field trials. |
| // Controls datagram transport support. |
| const char kDatagramTransportFieldTrial[] = "WebRTC-DatagramTransport"; |
| // Controls datagram transport data channel support. |
| const char kDatagramTransportDataChannelFieldTrial[] = |
| "WebRTC-DatagramTransportDataChannels"; |
| |
| // UMA metric names. |
| const char kSimulcastVersionApplyLocalDescription[] = |
| "WebRTC.PeerConnection.Simulcast.ApplyLocalDescription"; |
| const char kSimulcastVersionApplyRemoteDescription[] = |
| "WebRTC.PeerConnection.Simulcast.ApplyRemoteDescription"; |
| const char kSimulcastNumberOfEncodings[] = |
| "WebRTC.PeerConnection.Simulcast.NumberOfSendEncodings"; |
| const char kSimulcastDisabled[] = "WebRTC.PeerConnection.Simulcast.Disabled"; |
| |
| static const char kDefaultStreamId[] = "default"; |
| static const char kDefaultAudioSenderId[] = "defaulta0"; |
| static const char kDefaultVideoSenderId[] = "defaultv0"; |
| |
| // The length of RTCP CNAMEs. |
| static const int kRtcpCnameLength = 16; |
| |
| enum { |
| MSG_SET_SESSIONDESCRIPTION_SUCCESS = 0, |
| MSG_SET_SESSIONDESCRIPTION_FAILED, |
| MSG_CREATE_SESSIONDESCRIPTION_FAILED, |
| MSG_GETSTATS, |
| MSG_REPORT_USAGE_PATTERN, |
| }; |
| |
| static const int REPORT_USAGE_PATTERN_DELAY_MS = 60000; |
| |
| struct SetSessionDescriptionMsg : public rtc::MessageData { |
| explicit SetSessionDescriptionMsg( |
| webrtc::SetSessionDescriptionObserver* observer) |
| : observer(observer) {} |
| |
| rtc::scoped_refptr<webrtc::SetSessionDescriptionObserver> observer; |
| RTCError error; |
| }; |
| |
| struct CreateSessionDescriptionMsg : public rtc::MessageData { |
| explicit CreateSessionDescriptionMsg( |
| webrtc::CreateSessionDescriptionObserver* observer) |
| : observer(observer) {} |
| |
| rtc::scoped_refptr<webrtc::CreateSessionDescriptionObserver> observer; |
| RTCError error; |
| }; |
| |
| struct GetStatsMsg : public rtc::MessageData { |
| GetStatsMsg(webrtc::StatsObserver* observer, |
| webrtc::MediaStreamTrackInterface* track) |
| : observer(observer), track(track) {} |
| rtc::scoped_refptr<webrtc::StatsObserver> observer; |
| rtc::scoped_refptr<webrtc::MediaStreamTrackInterface> track; |
| }; |
| |
| // Check if we can send |new_stream| on a PeerConnection. |
| bool CanAddLocalMediaStream(webrtc::StreamCollectionInterface* current_streams, |
| webrtc::MediaStreamInterface* new_stream) { |
| 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; |
| } |
| |
| // 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>(); |
| } |
| |
| bool IsValidOfferToReceiveMedia(int value) { |
| typedef PeerConnectionInterface::RTCOfferAnswerOptions Options; |
| return (value >= Options::kUndefined) && |
| (value <= Options::kMaxOfferToReceiveMedia); |
| } |
| |
| // Add options to |[audio/video]_media_description_options| from |senders|. |
| 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); |
| } |
| } |
| } |
| } |
| |
| // Add options to |session_options| from |rtp_data_channels|. |
| void AddRtpDataChannelOptions( |
| const std::map<std::string, rtc::scoped_refptr<DataChannel>>& |
| rtp_data_channels, |
| cricket::MediaDescriptionOptions* data_media_description_options) { |
| if (!data_media_description_options) { |
| return; |
| } |
| // Check for data channels. |
| for (const auto& kv : rtp_data_channels) { |
| const DataChannel* channel = kv.second; |
| if (channel->state() == DataChannel::kConnecting || |
| channel->state() == DataChannel::kOpen) { |
| // Legacy RTP data channels are signaled with the track/stream ID set to |
| // the data channel's label. |
| data_media_description_options->AddRtpDataChannel(channel->label(), |
| channel->label()); |
| } |
| } |
| } |
| |
| uint32_t ConvertIceTransportTypeToCandidateFilter( |
| PeerConnectionInterface::IceTransportsType type) { |
| switch (type) { |
| case PeerConnectionInterface::kNone: |
| return cricket::CF_NONE; |
| case PeerConnectionInterface::kRelay: |
| return cricket::CF_RELAY; |
| case PeerConnectionInterface::kNoHost: |
| return (cricket::CF_ALL & ~cricket::CF_HOST); |
| case PeerConnectionInterface::kAll: |
| return cricket::CF_ALL; |
| default: |
| RTC_NOTREACHED(); |
| } |
| return cricket::CF_NONE; |
| } |
| |
| std::string GetSignalingStateString( |
| PeerConnectionInterface::SignalingState state) { |
| switch (state) { |
| case PeerConnectionInterface::kStable: |
| return "kStable"; |
| case PeerConnectionInterface::kHaveLocalOffer: |
| return "kHaveLocalOffer"; |
| case PeerConnectionInterface::kHaveLocalPrAnswer: |
| return "kHavePrAnswer"; |
| case PeerConnectionInterface::kHaveRemoteOffer: |
| return "kHaveRemoteOffer"; |
| case PeerConnectionInterface::kHaveRemotePrAnswer: |
| return "kHaveRemotePrAnswer"; |
| case PeerConnectionInterface::kClosed: |
| return "kClosed"; |
| } |
| RTC_NOTREACHED(); |
| return ""; |
| } |
| |
| IceCandidatePairType GetIceCandidatePairCounter( |
| const cricket::Candidate& local, |
| const cricket::Candidate& remote) { |
| const auto& l = local.type(); |
| const auto& r = remote.type(); |
| const auto& host = LOCAL_PORT_TYPE; |
| const auto& srflx = STUN_PORT_TYPE; |
| const auto& relay = RELAY_PORT_TYPE; |
| const auto& prflx = PRFLX_PORT_TYPE; |
| if (l == host && r == host) { |
| bool local_hostname = |
| !local.address().hostname().empty() && local.address().IsUnresolvedIP(); |
| bool remote_hostname = !remote.address().hostname().empty() && |
| remote.address().IsUnresolvedIP(); |
| bool local_private = IPIsPrivate(local.address().ipaddr()); |
| bool remote_private = IPIsPrivate(remote.address().ipaddr()); |
| if (local_hostname) { |
| if (remote_hostname) { |
| return kIceCandidatePairHostNameHostName; |
| } else if (remote_private) { |
| return kIceCandidatePairHostNameHostPrivate; |
| } else { |
| return kIceCandidatePairHostNameHostPublic; |
| } |
| } else if (local_private) { |
| if (remote_hostname) { |
| return kIceCandidatePairHostPrivateHostName; |
| } else if (remote_private) { |
| return kIceCandidatePairHostPrivateHostPrivate; |
| } else { |
| return kIceCandidatePairHostPrivateHostPublic; |
| } |
| } else { |
| if (remote_hostname) { |
| return kIceCandidatePairHostPublicHostName; |
| } else if (remote_private) { |
| return kIceCandidatePairHostPublicHostPrivate; |
| } else { |
| return kIceCandidatePairHostPublicHostPublic; |
| } |
| } |
| } |
| if (l == host && r == srflx) |
| return kIceCandidatePairHostSrflx; |
| if (l == host && r == relay) |
| return kIceCandidatePairHostRelay; |
| if (l == host && r == prflx) |
| return kIceCandidatePairHostPrflx; |
| if (l == srflx && r == host) |
| return kIceCandidatePairSrflxHost; |
| if (l == srflx && r == srflx) |
| return kIceCandidatePairSrflxSrflx; |
| if (l == srflx && r == relay) |
| return kIceCandidatePairSrflxRelay; |
| if (l == srflx && r == prflx) |
| return kIceCandidatePairSrflxPrflx; |
| if (l == relay && r == host) |
| return kIceCandidatePairRelayHost; |
| if (l == relay && r == srflx) |
| return kIceCandidatePairRelaySrflx; |
| if (l == relay && r == relay) |
| return kIceCandidatePairRelayRelay; |
| if (l == relay && r == prflx) |
| return kIceCandidatePairRelayPrflx; |
| if (l == prflx && r == host) |
| return kIceCandidatePairPrflxHost; |
| if (l == prflx && r == srflx) |
| return kIceCandidatePairPrflxSrflx; |
| if (l == prflx && r == relay) |
| return kIceCandidatePairPrflxRelay; |
| return kIceCandidatePairMax; |
| } |
| |
| // 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 |
| // rejected. |old_content_one| and |old_content_two| refer to the m= section |
| // 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)); |
| } |
| |
| // 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 |
| // check against the local and remote descriptions stored from the last |
| // negotiation, because either of these could be the most up to date for |
| // possible rejected m sections. These are the |current_desc| and |
| // |secondary_current_desc|. |
| 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(); |
| } |
| |
| void NoteKeyProtocolAndMedia(KeyExchangeProtocolType protocol_type, |
| cricket::MediaType media_type) { |
| // Array of structs needed to map {KeyExchangeProtocolType, |
| // cricket::MediaType} to KeyExchangeProtocolMedia without using std::map in |
| // order to avoid -Wglobal-constructors and -Wexit-time-destructors. |
| static constexpr struct { |
| KeyExchangeProtocolType protocol_type; |
| cricket::MediaType media_type; |
| KeyExchangeProtocolMedia protocol_media; |
| } kEnumCounterKeyProtocolMediaMap[] = { |
| {kEnumCounterKeyProtocolDtls, cricket::MEDIA_TYPE_AUDIO, |
| kEnumCounterKeyProtocolMediaTypeDtlsAudio}, |
| {kEnumCounterKeyProtocolDtls, cricket::MEDIA_TYPE_VIDEO, |
| kEnumCounterKeyProtocolMediaTypeDtlsVideo}, |
| {kEnumCounterKeyProtocolDtls, cricket::MEDIA_TYPE_DATA, |
| kEnumCounterKeyProtocolMediaTypeDtlsData}, |
| {kEnumCounterKeyProtocolSdes, cricket::MEDIA_TYPE_AUDIO, |
| kEnumCounterKeyProtocolMediaTypeSdesAudio}, |
| {kEnumCounterKeyProtocolSdes, cricket::MEDIA_TYPE_VIDEO, |
| kEnumCounterKeyProtocolMediaTypeSdesVideo}, |
| {kEnumCounterKeyProtocolSdes, cricket::MEDIA_TYPE_DATA, |
| kEnumCounterKeyProtocolMediaTypeSdesData}, |
| }; |
| |
| RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.KeyProtocol", protocol_type, |
| kEnumCounterKeyProtocolMax); |
| |
| for (const auto& i : kEnumCounterKeyProtocolMediaMap) { |
| if (i.protocol_type == protocol_type && i.media_type == media_type) { |
| RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.KeyProtocolByMedia", |
| i.protocol_media, |
| kEnumCounterKeyProtocolMediaTypeMax); |
| } |
| } |
| } |
| |
| void NoteAddIceCandidateResult(int result) { |
| RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.AddIceCandidate", result, |
| kAddIceCandidateMax); |
| } |
| |
| // Checks that each non-rejected content has SDES crypto keys or a DTLS |
| // 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 |
| // by Channel's |srtp_required| check. |
| RTCError VerifyCrypto(const SessionDescription* desc, bool dtls_enabled) { |
| const cricket::ContentGroup* bundle = |
| desc->GetGroupByName(cricket::GROUP_TYPE_BUNDLE); |
| for (const cricket::ContentInfo& content_info : desc->contents()) { |
| if (content_info.rejected) { |
| continue; |
| } |
| // Note what media is used with each crypto protocol, for all sections. |
| NoteKeyProtocolAndMedia(dtls_enabled ? webrtc::kEnumCounterKeyProtocolDtls |
| : webrtc::kEnumCounterKeyProtocolSdes, |
| content_info.media_description()->type()); |
| const std::string& mid = content_info.name; |
| if (bundle && bundle->HasContentName(mid) && |
| mid != *(bundle->FirstContentName())) { |
| // 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); |
| } |
| if (dtls_enabled) { |
| if (!tinfo->description.identity_fingerprint) { |
| RTC_LOG(LS_WARNING) |
| << "Session description must have DTLS fingerprint if " |
| "DTLS enabled."; |
| return RTCError(RTCErrorType::INVALID_PARAMETER, |
| kSdpWithoutDtlsFingerprint); |
| } |
| } else { |
| if (media->cryptos().empty()) { |
| RTC_LOG(LS_WARNING) |
| << "Session description must have SDES when DTLS disabled."; |
| return RTCError(RTCErrorType::INVALID_PARAMETER, kSdpWithoutSdesCrypto); |
| } |
| } |
| } |
| 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. |
| bool VerifyIceUfragPwdPresent(const SessionDescription* desc) { |
| const cricket::ContentGroup* bundle = |
| desc->GetGroupByName(cricket::GROUP_TYPE_BUNDLE); |
| for (const cricket::ContentInfo& content_info : desc->contents()) { |
| if (content_info.rejected) { |
| continue; |
| } |
| const std::string& mid = content_info.name; |
| if (bundle && bundle->HasContentName(mid) && |
| mid != *(bundle->FirstContentName())) { |
| // 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; |
| } |
| |
| // Returns true if |new_desc| requests an ICE restart (i.e., new ufrag/pwd). |
| 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") |
| << " " << SdpTypeToString(type) << " sdp: " << error.message(); |
| 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; |
| } |
| |
| absl::optional<int> RTCConfigurationToIceConfigOptionalInt( |
| int rtc_configuration_parameter) { |
| if (rtc_configuration_parameter == |
| webrtc::PeerConnectionInterface::RTCConfiguration::kUndefined) { |
| return absl::nullopt; |
| } |
| return rtc_configuration_parameter; |
| } |
| |
| void ReportSimulcastApiVersion(const char* name, |
| const SessionDescription& session) { |
| bool has_legacy = false; |
| bool has_spec_compliant = false; |
| for (const ContentInfo& content : session.contents()) { |
| if (!content.media_description()) { |
| continue; |
| } |
| has_spec_compliant |= content.media_description()->HasSimulcast(); |
| for (const StreamParams& sp : content.media_description()->streams()) { |
| has_legacy |= sp.has_ssrc_group(cricket::kSimSsrcGroupSemantics); |
| } |
| } |
| |
| if (has_legacy) { |
| RTC_HISTOGRAM_ENUMERATION(name, kSimulcastApiVersionLegacy, |
| kSimulcastApiVersionMax); |
| } |
| if (has_spec_compliant) { |
| RTC_HISTOGRAM_ENUMERATION(name, kSimulcastApiVersionSpecCompliant, |
| kSimulcastApiVersionMax); |
| } |
| if (!has_legacy && !has_spec_compliant) { |
| RTC_HISTOGRAM_ENUMERATION(name, kSimulcastApiVersionNone, |
| kSimulcastApiVersionMax); |
| } |
| } |
| |
| const ContentInfo* FindTransceiverMSection( |
| RtpTransceiverProxyWithInternal<RtpTransceiver>* transceiver, |
| const SessionDescriptionInterface* session_description) { |
| return transceiver->mid() |
| ? session_description->description()->GetContentByName( |
| *transceiver->mid()) |
| : nullptr; |
| } |
| |
| // 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 { |
| RTC_DCHECK(was_called_); |
| } |
| |
| void OnSuccess(SessionDescriptionInterface* desc) override { |
| RTC_DCHECK(!was_called_); |
| #ifdef RTC_DCHECK_IS_ON |
| 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 { |
| RTC_DCHECK(!was_called_); |
| #ifdef RTC_DCHECK_IS_ON |
| was_called_ = true; |
| #endif // RTC_DCHECK_IS_ON |
| operation_complete_callback_(); |
| observer_->OnFailure(std::move(error)); |
| } |
| |
| private: |
| #ifdef RTC_DCHECK_IS_ON |
| bool was_called_ = false; |
| #endif // RTC_DCHECK_IS_ON |
| rtc::scoped_refptr<CreateSessionDescriptionObserver> observer_; |
| std::function<void()> operation_complete_callback_; |
| }; |
| |
| } // namespace |
| |
| // Used by parameterless SetLocalDescription() to create an offer or answer. |
| // Upon completion of creating the session description, SetLocalDescription() is |
| // invoked with the result. |
| // For consistency with DoSetLocalDescription(), if the PeerConnection is |
| // destroyed midst operation, we DO NOT inform the |
| // |set_local_description_observer| that the operation failed. |
| // TODO(hbos): If/when we process SLD messages in ~PeerConnection, the |
| // consistent thing would be to inform the observer here. |
| class PeerConnection::ImplicitCreateSessionDescriptionObserver |
| : public CreateSessionDescriptionObserver { |
| public: |
| ImplicitCreateSessionDescriptionObserver( |
| rtc::WeakPtr<PeerConnection> pc, |
| rtc::scoped_refptr<SetSessionDescriptionObserver> |
| set_local_description_observer) |
| : pc_(std::move(pc)), |
| 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; |
| |
| // Abort early if |pc_| is no longer valid. |
| if (!pc_) { |
| operation_complete_callback_(); |
| return; |
| } |
| // DoSetLocalDescription() is currently implemented as a synchronous |
| // operation but where the |set_local_description_observer_|'s callbacks are |
| // invoked asynchronously in a post to PeerConnection::OnMessage(). |
| pc_->DoSetLocalDescription(std::move(desc), |
| std::move(set_local_description_observer_)); |
| // For backwards-compatability reasons, we declare the operation as |
| // completed here (rather than in PeerConnection::OnMessage()). This ensures |
| // that subsequent offer/answer operations can start immediately (without |
| // waiting for OnMessage()). |
| operation_complete_callback_(); |
| } |
| |
| void OnFailure(RTCError error) override { |
| RTC_DCHECK(!was_called_); |
| was_called_ = true; |
| |
| // Abort early if |pc_| is no longer valid. |
| if (!pc_) { |
| operation_complete_callback_(); |
| return; |
| } |
| // DoSetLocalDescription() reports its failures in a post. We do the |
| // same thing here for consistency. |
| pc_->PostSetSessionDescriptionFailure( |
| set_local_description_observer_, |
| RTCError(error.type(), |
| std::string("SetLocalDescription failed to create " |
| "session description - ") + |
| error.message())); |
| operation_complete_callback_(); |
| } |
| |
| private: |
| bool was_called_ = false; |
| rtc::WeakPtr<PeerConnection> pc_; |
| rtc::scoped_refptr<SetSessionDescriptionObserver> |
| set_local_description_observer_; |
| std::function<void()> operation_complete_callback_; |
| }; |
| |
| class PeerConnection::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(); } |
| |
| // Returns true if |local_description| shares no ICE credentials with the |
| // 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_; |
| }; |
| |
| // Upon completion, posts a task to execute the callback of the |
| // SetSessionDescriptionObserver asynchronously on the same thread. At this |
| // point, the state of the peer connection might no longer reflect the effects |
| // of the SetRemoteDescription operation, as the peer connection could have been |
| // modified during the post. |
| // TODO(hbos): Remove this class once we remove the version of |
| // PeerConnectionInterface::SetRemoteDescription() that takes a |
| // SetSessionDescriptionObserver as an argument. |
| class PeerConnection::SetRemoteDescriptionObserverAdapter |
| : public rtc::RefCountedObject<SetRemoteDescriptionObserverInterface> { |
| public: |
| SetRemoteDescriptionObserverAdapter( |
| rtc::scoped_refptr<PeerConnection> pc, |
| rtc::scoped_refptr<SetSessionDescriptionObserver> wrapper) |
| : pc_(std::move(pc)), wrapper_(std::move(wrapper)) {} |
| |
| // SetRemoteDescriptionObserverInterface implementation. |
| void OnSetRemoteDescriptionComplete(RTCError error) override { |
| if (error.ok()) |
| pc_->PostSetSessionDescriptionSuccess(wrapper_); |
| else |
| pc_->PostSetSessionDescriptionFailure(wrapper_, std::move(error)); |
| } |
| |
| private: |
| rtc::scoped_refptr<PeerConnection> pc_; |
| rtc::scoped_refptr<SetSessionDescriptionObserver> wrapper_; |
| }; |
| |
| bool PeerConnectionInterface::RTCConfiguration::operator==( |
| const PeerConnectionInterface::RTCConfiguration& o) const { |
| // This static_assert prevents us from accidentally breaking operator==. |
| // Note: Order matters! Fields must be ordered the same as RTCConfiguration. |
| struct stuff_being_tested_for_equality { |
| IceServers servers; |
| IceTransportsType type; |
| BundlePolicy bundle_policy; |
| RtcpMuxPolicy rtcp_mux_policy; |
| std::vector<rtc::scoped_refptr<rtc::RTCCertificate>> certificates; |
| int ice_candidate_pool_size; |
| bool disable_ipv6; |
| bool disable_ipv6_on_wifi; |
| int max_ipv6_networks; |
| bool disable_link_local_networks; |
| bool enable_rtp_data_channel; |
| absl::optional<int> screencast_min_bitrate; |
| absl::optional<bool> combined_audio_video_bwe; |
| absl::optional<bool> enable_dtls_srtp; |
| TcpCandidatePolicy tcp_candidate_policy; |
| CandidateNetworkPolicy candidate_network_policy; |
| int audio_jitter_buffer_max_packets; |
| bool audio_jitter_buffer_fast_accelerate; |
| int audio_jitter_buffer_min_delay_ms; |
| bool audio_jitter_buffer_enable_rtx_handling; |
| int ice_connection_receiving_timeout; |
| int ice_backup_candidate_pair_ping_interval; |
| ContinualGatheringPolicy continual_gathering_policy; |
| bool prioritize_most_likely_ice_candidate_pairs; |
| struct cricket::MediaConfig media_config; |
| bool prune_turn_ports; |
| PortPrunePolicy turn_port_prune_policy; |
| bool presume_writable_when_fully_relayed; |
| bool enable_ice_renomination; |
| bool redetermine_role_on_ice_restart; |
| bool surface_ice_candidates_on_ice_transport_type_changed; |
| absl::optional<int> ice_check_interval_strong_connectivity; |
| absl::optional<int> ice_check_interval_weak_connectivity; |
| absl::optional<int> ice_check_min_interval; |
| absl::optional<int> ice_unwritable_timeout; |
| absl::optional<int> ice_unwritable_min_checks; |
| absl::optional<int> ice_inactive_timeout; |
| absl::optional<int> stun_candidate_keepalive_interval; |
| webrtc::TurnCustomizer* turn_customizer; |
| SdpSemantics sdp_semantics; |
| absl::optional<rtc::AdapterType> network_preference; |
| bool active_reset_srtp_params; |
| bool use_media_transport; |
| bool use_media_transport_for_data_channels; |
| absl::optional<bool> use_datagram_transport; |
| absl::optional<bool> use_datagram_transport_for_data_channels; |
| absl::optional<bool> use_datagram_transport_for_data_channels_receive_only; |
| absl::optional<CryptoOptions> crypto_options; |
| bool offer_extmap_allow_mixed; |
| std::string turn_logging_id; |
| bool enable_implicit_rollback; |
| absl::optional<bool> allow_codec_switching; |
| }; |
| static_assert(sizeof(stuff_being_tested_for_equality) == sizeof(*this), |
| "Did you add something to RTCConfiguration and forget to " |
| "update operator==?"); |
| return type == o.type && servers == o.servers && |
| bundle_policy == o.bundle_policy && |
| rtcp_mux_policy == o.rtcp_mux_policy && |
| tcp_candidate_policy == o.tcp_candidate_policy && |
| candidate_network_policy == o.candidate_network_policy && |
| audio_jitter_buffer_max_packets == o.audio_jitter_buffer_max_packets && |
| audio_jitter_buffer_fast_accelerate == |
| o.audio_jitter_buffer_fast_accelerate && |
| audio_jitter_buffer_min_delay_ms == |
| o.audio_jitter_buffer_min_delay_ms && |
| audio_jitter_buffer_enable_rtx_handling == |
| o.audio_jitter_buffer_enable_rtx_handling && |
| ice_connection_receiving_timeout == |
| o.ice_connection_receiving_timeout && |
| ice_backup_candidate_pair_ping_interval == |
| o.ice_backup_candidate_pair_ping_interval && |
| continual_gathering_policy == o.continual_gathering_policy && |
| certificates == o.certificates && |
| prioritize_most_likely_ice_candidate_pairs == |
| o.prioritize_most_likely_ice_candidate_pairs && |
| media_config == o.media_config && disable_ipv6 == o.disable_ipv6 && |
| disable_ipv6_on_wifi == o.disable_ipv6_on_wifi && |
| max_ipv6_networks == o.max_ipv6_networks && |
| disable_link_local_networks == o.disable_link_local_networks && |
| enable_rtp_data_channel == o.enable_rtp_data_channel && |
| screencast_min_bitrate == o.screencast_min_bitrate && |
| combined_audio_video_bwe == o.combined_audio_video_bwe && |
| enable_dtls_srtp == o.enable_dtls_srtp && |
| ice_candidate_pool_size == o.ice_candidate_pool_size && |
| prune_turn_ports == o.prune_turn_ports && |
| turn_port_prune_policy == o.turn_port_prune_policy && |
| presume_writable_when_fully_relayed == |
| o.presume_writable_when_fully_relayed && |
| enable_ice_renomination == o.enable_ice_renomination && |
| redetermine_role_on_ice_restart == o.redetermine_role_on_ice_restart && |
| surface_ice_candidates_on_ice_transport_type_changed == |
| o.surface_ice_candidates_on_ice_transport_type_changed && |
| ice_check_interval_strong_connectivity == |
| o.ice_check_interval_strong_connectivity && |
| ice_check_interval_weak_connectivity == |
| o.ice_check_interval_weak_connectivity && |
| ice_check_min_interval == o.ice_check_min_interval && |
| ice_unwritable_timeout == o.ice_unwritable_timeout && |
| ice_unwritable_min_checks == o.ice_unwritable_min_checks && |
| ice_inactive_timeout == o.ice_inactive_timeout && |
| stun_candidate_keepalive_interval == |
| o.stun_candidate_keepalive_interval && |
| turn_customizer == o.turn_customizer && |
| sdp_semantics == o.sdp_semantics && |
| network_preference == o.network_preference && |
| active_reset_srtp_params == o.active_reset_srtp_params && |
| use_media_transport == o.use_media_transport && |
| use_media_transport_for_data_channels == |
| o.use_media_transport_for_data_channels && |
| use_datagram_transport == o.use_datagram_transport && |
| use_datagram_transport_for_data_channels == |
| o.use_datagram_transport_for_data_channels && |
| use_datagram_transport_for_data_channels_receive_only == |
| o.use_datagram_transport_for_data_channels_receive_only && |
| crypto_options == o.crypto_options && |
| offer_extmap_allow_mixed == o.offer_extmap_allow_mixed && |
| turn_logging_id == o.turn_logging_id && |
| enable_implicit_rollback == o.enable_implicit_rollback && |
| allow_codec_switching == o.allow_codec_switching && |
| enable_simulcast_stats == o.enable_simulcast_stats; |
| } |
| |
| bool PeerConnectionInterface::RTCConfiguration::operator!=( |
| const PeerConnectionInterface::RTCConfiguration& o) const { |
| return !(*this == o); |
| } |
| |
| void PeerConnection::TransceiverStableState::set_newly_created() { |
| RTC_DCHECK(!has_m_section_); |
| newly_created_ = true; |
| } |
| |
| void PeerConnection::TransceiverStableState::SetMSectionIfUnset( |
| absl::optional<std::string> mid, |
| absl::optional<size_t> mline_index) { |
| if (!has_m_section_) { |
| mid_ = mid; |
| mline_index_ = mline_index; |
| has_m_section_ = true; |
| } |
| } |
| |
| void PeerConnection::TransceiverStableState::SetRemoteStreamIdsIfUnset( |
| const std::vector<std::string>& ids) { |
| if (!remote_stream_ids_.has_value()) { |
| remote_stream_ids_ = ids; |
| } |
| } |
| |
| // 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."; |
| RTC_NOTREACHED(); |
| } |
| return cname; |
| } |
| |
| bool ValidateOfferAnswerOptions( |
| const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options) { |
| return IsValidOfferToReceiveMedia(rtc_options.offer_to_receive_audio) && |
| IsValidOfferToReceiveMedia(rtc_options.offer_to_receive_video); |
| } |
| |
| // From |rtc_options|, fill parts of |session_options| shared by all generated |
| // m= sections (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; |
| } |
| |
| PeerConnection::PeerConnection(PeerConnectionFactory* factory, |
| std::unique_ptr<RtcEventLog> event_log, |
| std::unique_ptr<Call> call) |
| : factory_(factory), |
| event_log_(std::move(event_log)), |
| event_log_ptr_(event_log_.get()), |
| operations_chain_(rtc::OperationsChain::Create()), |
| datagram_transport_config_( |
| field_trial::FindFullName(kDatagramTransportFieldTrial)), |
| datagram_transport_data_channel_config_( |
| field_trial::FindFullName(kDatagramTransportDataChannelFieldTrial)), |
| rtcp_cname_(GenerateRtcpCname()), |
| local_streams_(StreamCollection::Create()), |
| remote_streams_(StreamCollection::Create()), |
| call_(std::move(call)), |
| call_ptr_(call_.get()), |
| local_ice_credentials_to_replace_(new LocalIceCredentialsToReplace()), |
| data_channel_controller_(this), |
| weak_ptr_factory_(this) {} |
| |
| PeerConnection::~PeerConnection() { |
| TRACE_EVENT0("webrtc", "PeerConnection::~PeerConnection"); |
| RTC_DCHECK_RUN_ON(signaling_thread()); |
| |
| weak_ptr_factory_.InvalidateWeakPtrs(); |
| |
| // Need to stop transceivers before destroying the stats collector because |
| // AudioRtpSender has a reference to the StatsCollector it will update when |
| // stopping. |
| for (const auto& transceiver : transceivers_) { |
| transceiver->Stop(); |
| } |
| |
| stats_.reset(nullptr); |
| if (stats_collector_) { |
| stats_collector_->WaitForPendingRequest(); |
| stats_collector_ = nullptr; |
| } |
| |
| // Don't destroy BaseChannels until after stats has been cleaned up so that |
| // the last stats request can still read from the channels. |
| DestroyAllChannels(); |
| |
| RTC_LOG(LS_INFO) << "Session: " << session_id() << " is destroyed."; |
| |
| webrtc_session_desc_factory_.reset(); |
| sctp_factory_.reset(); |
| transport_controller_.reset(); |
| |
| // port_allocator_ lives on the network thread and should be destroyed there. |
| network_thread()->Invoke<void>(RTC_FROM_HERE, [this] { |
| RTC_DCHECK_RUN_ON(network_thread()); |
| port_allocator_.reset(); |
| }); |
| // call_ and event_log_ must be destroyed on the worker thread. |
| worker_thread()->Invoke<void>(RTC_FROM_HERE, [this] { |
| RTC_DCHECK_RUN_ON(worker_thread()); |
| call_.reset(); |
| // The event log must outlive call (and any other object that uses it). |
| event_log_.reset(); |
| }); |
| |
| // Process all pending notifications in the message queue. If we don't do |
| // this, requests will linger and not know they succeeded or failed. |
| rtc::MessageList list; |
| signaling_thread()->Clear(this, rtc::MQID_ANY, &list); |
| for (auto& msg : list) { |
| if (msg.message_id == MSG_CREATE_SESSIONDESCRIPTION_FAILED) { |
| // Processing CreateOffer() and CreateAnswer() messages ensures their |
| // observers are invoked even if the PeerConnection is destroyed early. |
| OnMessage(&msg); |
| } else { |
| // TODO(hbos): Consider processing all pending messages. This would mean |
| // that SetLocalDescription() and SetRemoteDescription() observers are |
| // informed of successes and failures; this is currently NOT the case. |
| delete msg.pdata; |
| } |
| } |
| } |
| |
| void PeerConnection::DestroyAllChannels() { |
| // Destroy video channels first since they may have a pointer to a voice |
| // channel. |
| for (const auto& transceiver : transceivers_) { |
| if (transceiver->media_type() == cricket::MEDIA_TYPE_VIDEO) { |
| DestroyTransceiverChannel(transceiver); |
| } |
| } |
| for (const auto& transceiver : transceivers_) { |
| if (transceiver->media_type() == cricket::MEDIA_TYPE_AUDIO) { |
| DestroyTransceiverChannel(transceiver); |
| } |
| } |
| DestroyDataChannelTransport(); |
| } |
| |
| bool PeerConnection::Initialize( |
| const PeerConnectionInterface::RTCConfiguration& configuration, |
| PeerConnectionDependencies dependencies) { |
| RTC_DCHECK_RUN_ON(signaling_thread()); |
| TRACE_EVENT0("webrtc", "PeerConnection::Initialize"); |
| |
| RTCError config_error = ValidateConfiguration(configuration); |
| if (!config_error.ok()) { |
| RTC_LOG(LS_ERROR) << "Invalid configuration: " << config_error.message(); |
| return false; |
| } |
| |
| if (!dependencies.allocator) { |
| RTC_LOG(LS_ERROR) |
| << "PeerConnection initialized without a PortAllocator? " |
| "This shouldn't happen if using PeerConnectionFactory."; |
| return false; |
| } |
| |
| if (!dependencies.observer) { |
| // TODO(deadbeef): Why do we do this? |
| RTC_LOG(LS_ERROR) << "PeerConnection initialized without a " |
| "PeerConnectionObserver"; |
| return false; |
| } |
| |
| observer_ = dependencies.observer; |
| async_resolver_factory_ = std::move(dependencies.async_resolver_factory); |
| port_allocator_ = std::move(dependencies.allocator); |
| packet_socket_factory_ = std::move(dependencies.packet_socket_factory); |
| ice_transport_factory_ = std::move(dependencies.ice_transport_factory); |
| tls_cert_verifier_ = std::move(dependencies.tls_cert_verifier); |
| |
| cricket::ServerAddresses stun_servers; |
| std::vector<cricket::RelayServerConfig> turn_servers; |
| |
| RTCErrorType parse_error = |
| ParseIceServers(configuration.servers, &stun_servers, &turn_servers); |
| if (parse_error != RTCErrorType::NONE) { |
| return false; |
| } |
| |
| // 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; |
| } |
| |
| // The port allocator lives on the network thread and should be initialized |
| // there. |
| const auto pa_result = |
| network_thread()->Invoke<InitializePortAllocatorResult>( |
| RTC_FROM_HERE, |
| rtc::Bind(&PeerConnection::InitializePortAllocator_n, this, |
| stun_servers, turn_servers, configuration)); |
| |
| // If initialization was successful, note if STUN or TURN servers |
| // were supplied. |
| if (!stun_servers.empty()) { |
| NoteUsageEvent(UsageEvent::STUN_SERVER_ADDED); |
| } |
| if (!turn_servers.empty()) { |
| NoteUsageEvent(UsageEvent::TURN_SERVER_ADDED); |
| } |
| |
| // Send information about IPv4/IPv6 status. |
| PeerConnectionAddressFamilyCounter address_family; |
| if (pa_result.enable_ipv6) { |
| address_family = kPeerConnection_IPv6; |
| } else { |
| address_family = kPeerConnection_IPv4; |
| } |
| RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.IPMetrics", address_family, |
| kPeerConnectionAddressFamilyCounter_Max); |
| |
| const PeerConnectionFactoryInterface::Options& options = factory_->options(); |
| |
| // 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); |
| JsepTransportController::Config config; |
| config.redetermine_role_on_ice_restart = |
| configuration.redetermine_role_on_ice_restart; |
| config.ssl_max_version = factory_->options().ssl_max_version; |
| config.disable_encryption = options.disable_encryption; |
| config.bundle_policy = configuration.bundle_policy; |
| config.rtcp_mux_policy = configuration.rtcp_mux_policy; |
| // TODO(bugs.webrtc.org/9891) - Remove options.crypto_options then remove this |
| // stub. |
| config.crypto_options = configuration.crypto_options.has_value() |
| ? *configuration.crypto_options |
| : options.crypto_options; |
| config.transport_observer = this; |
| // It's safe to pass |this| and using |rtcp_invoker_| and the |call_| pointer |
| // since the JsepTransportController instance is owned by this PeerConnection |
| // instance and is destroyed before both |rtcp_invoker_| and the |call_| |
| // pointer. |
| config.rtcp_handler = [this](const rtc::CopyOnWriteBuffer& packet, |
| int64_t packet_time_us) { |
| RTC_DCHECK_RUN_ON(network_thread()); |
| rtcp_invoker_.AsyncInvoke<void>( |
| RTC_FROM_HERE, worker_thread(), [this, packet, packet_time_us] { |
| RTC_DCHECK_RUN_ON(worker_thread()); |
| // |call_| is reset on the worker thread in the PeerConnection |
| // destructor, so we check that it's still valid before propagating |
| // the packet. |
| if (call_) { |
| call_->Receiver()->DeliverPacket(MediaType::ANY, packet, |
| packet_time_us); |
| } |
| }); |
| }; |
| config.event_log = event_log_ptr_; |
| #if defined(ENABLE_EXTERNAL_AUTH) |
| config.enable_external_auth = true; |
| #endif |
| config.active_reset_srtp_params = configuration.active_reset_srtp_params; |
| |
| use_datagram_transport_ = datagram_transport_config_.enabled && |
| configuration.use_datagram_transport.value_or( |
| datagram_transport_config_.default_value); |
| use_datagram_transport_for_data_channels_ = |
| datagram_transport_data_channel_config_.enabled && |
| configuration.use_datagram_transport_for_data_channels.value_or( |
| datagram_transport_data_channel_config_.default_value); |
| use_datagram_transport_for_data_channels_receive_only_ = |
| configuration.use_datagram_transport_for_data_channels_receive_only |
| .value_or(datagram_transport_data_channel_config_.receive_only); |
| if (use_datagram_transport_ || use_datagram_transport_for_data_channels_) { |
| if (!factory_->media_transport_factory()) { |
| RTC_DCHECK(false) |
| << "PeerConnecton is initialized with use_datagram_transport = true " |
| "or use_datagram_transport_for_data_channels = true " |
| "but media transport factory is not set in PeerConnectionFactory"; |
| return false; |
| } |
| |
| config.use_datagram_transport = use_datagram_transport_; |
| config.use_datagram_transport_for_data_channels = |
| use_datagram_transport_for_data_channels_; |
| config.use_datagram_transport_for_data_channels_receive_only = |
| use_datagram_transport_for_data_channels_receive_only_; |
| config.media_transport_factory = factory_->media_transport_factory(); |
| } |
| |
| // 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]; |
| } |
| |
| if (options.disable_encryption) { |
| dtls_enabled_ = false; |
| } else { |
| // Enable DTLS by default if we have an identity store or a certificate. |
| dtls_enabled_ = (dependencies.cert_generator || certificate); |
| // |configuration| can override the default |dtls_enabled_| value. |
| if (configuration.enable_dtls_srtp) { |
| dtls_enabled_ = *(configuration.enable_dtls_srtp); |
| } |
| } |
| |
| sctp_factory_ = factory_->CreateSctpTransportInternalFactory(); |
| |
| if (use_datagram_transport_for_data_channels_) { |
| if (configuration.enable_rtp_data_channel) { |
| RTC_LOG(LS_ERROR) << "enable_rtp_data_channel and " |
| "use_datagram_transport_for_data_channels are " |
| "incompatible and cannot both be set to true"; |
| return false; |
| } |
| if (configuration.enable_dtls_srtp && !*configuration.enable_dtls_srtp) { |
| RTC_LOG(LS_INFO) << "Using data channel transport with no fallback"; |
| data_channel_controller_.set_data_channel_type( |
| cricket::DCT_DATA_CHANNEL_TRANSPORT); |
| } else { |
| RTC_LOG(LS_INFO) << "Using data channel transport with fallback to SCTP"; |
| data_channel_controller_.set_data_channel_type( |
| cricket::DCT_DATA_CHANNEL_TRANSPORT_SCTP); |
| config.sctp_factory = sctp_factory_.get(); |
| } |
| } else if (configuration.enable_rtp_data_channel) { |
| // Enable creation of RTP data channels if the kEnableRtpDataChannels is |
| // set. It takes precendence over the disable_sctp_data_channels |
| // PeerConnectionFactoryInterface::Options. |
| data_channel_controller_.set_data_channel_type(cricket::DCT_RTP); |
| } else { |
| // DTLS has to be enabled to use SCTP. |
| if (!options.disable_sctp_data_channels && dtls_enabled_) { |
| data_channel_controller_.set_data_channel_type(cricket::DCT_SCTP); |
| config.sctp_factory = sctp_factory_.get(); |
| } |
| } |
| |
| config.ice_transport_factory = ice_transport_factory_.get(); |
| |
| transport_controller_.reset(new JsepTransportController( |
| signaling_thread(), network_thread(), port_allocator_.get(), |
| async_resolver_factory_.get(), config)); |
| transport_controller_->SignalIceConnectionState.connect( |
| this, &PeerConnection::OnTransportControllerConnectionState); |
| transport_controller_->SignalStandardizedIceConnectionState.connect( |
| this, &PeerConnection::SetStandardizedIceConnectionState); |
| transport_controller_->SignalConnectionState.connect( |
| this, &PeerConnection::SetConnectionState); |
| transport_controller_->SignalIceGatheringState.connect( |
| this, &PeerConnection::OnTransportControllerGatheringState); |
| transport_controller_->SignalIceCandidatesGathered.connect( |
| this, &PeerConnection::OnTransportControllerCandidatesGathered); |
| transport_controller_->SignalIceCandidateError.connect( |
| this, &PeerConnection::OnTransportControllerCandidateError); |
| transport_controller_->SignalIceCandidatesRemoved.connect( |
| this, &PeerConnection::OnTransportControllerCandidatesRemoved); |
| transport_controller_->SignalDtlsHandshakeError.connect( |
| this, &PeerConnection::OnTransportControllerDtlsHandshakeError); |
| transport_controller_->SignalIceCandidatePairChanged.connect( |
| this, &PeerConnection::OnTransportControllerCandidateChanged); |
| |
| stats_.reset(new StatsCollector(this)); |
| stats_collector_ = RTCStatsCollector::Create(this); |
| |
| configuration_ = configuration; |
| |
| transport_controller_->SetIceConfig(ParseIceConfig(configuration)); |
| |
| video_options_.screencast_min_bitrate_kbps = |
| configuration.screencast_min_bitrate; |
| audio_options_.combined_audio_video_bwe = |
| configuration.combined_audio_video_bwe; |
| |
| 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; |
| |
| audio_options_.audio_jitter_buffer_enable_rtx_handling = |
| configuration.audio_jitter_buffer_enable_rtx_handling; |
| |
| // Whether the certificate generator/certificate is null or not determines |
| // what PeerConnectionDescriptionFactory will do, so make sure that we give it |
| // the right instructions by clearing the variables if needed. |
| if (!dtls_enabled_) { |
| dependencies.cert_generator.reset(); |
| certificate = nullptr; |
| } else if (certificate) { |
| // Favor generated certificate over the certificate generator. |
| dependencies.cert_generator.reset(); |
| } |
| |
| webrtc_session_desc_factory_.reset(new WebRtcSessionDescriptionFactory( |
| signaling_thread(), channel_manager(), this, session_id(), |
| std::move(dependencies.cert_generator), certificate, &ssrc_generator_)); |
| webrtc_session_desc_factory_->SignalCertificateReady.connect( |
| this, &PeerConnection::OnCertificateReady); |
| |
| if (options.disable_encryption) { |
| webrtc_session_desc_factory_->SetSdesPolicy(cricket::SEC_DISABLED); |
| } |
| |
| webrtc_session_desc_factory_->set_enable_encrypted_rtp_header_extensions( |
| GetCryptoOptions().srtp.enable_encrypted_rtp_header_extensions); |
| webrtc_session_desc_factory_->set_is_unified_plan(IsUnifiedPlan()); |
| |
| // Add default audio/video transceivers for Plan B SDP. |
| if (!IsUnifiedPlan()) { |
| transceivers_.push_back( |
| RtpTransceiverProxyWithInternal<RtpTransceiver>::Create( |
| signaling_thread(), new RtpTransceiver(cricket::MEDIA_TYPE_AUDIO))); |
| transceivers_.push_back( |
| RtpTransceiverProxyWithInternal<RtpTransceiver>::Create( |
| signaling_thread(), new RtpTransceiver(cricket::MEDIA_TYPE_VIDEO))); |
| } |
| int delay_ms = |
| return_histogram_very_quickly_ ? 0 : REPORT_USAGE_PATTERN_DELAY_MS; |
| signaling_thread()->PostDelayed(RTC_FROM_HERE, delay_ms, this, |
| MSG_REPORT_USAGE_PATTERN, nullptr); |
| |
| if (dependencies.video_bitrate_allocator_factory) { |
| video_bitrate_allocator_factory_ = |
| std::move(dependencies.video_bitrate_allocator_factory); |
| } else { |
| video_bitrate_allocator_factory_ = |
| CreateBuiltinVideoBitrateAllocatorFactory(); |
| } |
| return true; |
| } |
| |
| RTCError PeerConnection::ValidateConfiguration( |
| const RTCConfiguration& config) const { |
| return cricket::P2PTransportChannel::ValidateIceConfig( |
| ParseIceConfig(config)); |
| } |
| |
| 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 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 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 (IsClosed()) { |
| return false; |
| } |
| if (!CanAddLocalMediaStream(local_streams_, local_stream)) { |
| return false; |
| } |
| |
| local_streams_->AddStream(local_stream); |
| MediaStreamObserver* observer = new MediaStreamObserver(local_stream); |
| observer->SignalAudioTrackAdded.connect(this, |
| &PeerConnection::OnAudioTrackAdded); |
| observer->SignalAudioTrackRemoved.connect( |
| this, &PeerConnection::OnAudioTrackRemoved); |
| observer->SignalVideoTrackAdded.connect(this, |
| &PeerConnection::OnVideoTrackAdded); |
| observer->SignalVideoTrackRemoved.connect( |
| this, &PeerConnection::OnVideoTrackRemoved); |
| stream_observers_.push_back(std::unique_ptr<MediaStreamObserver>(observer)); |
| |
| for (const auto& track : local_stream->GetAudioTracks()) { |
| AddAudioTrack(track.get(), local_stream); |
| } |
| for (const auto& track : local_stream->GetVideoTracks()) { |
| AddVideoTrack(track.get(), local_stream); |
| } |
| |
| stats_->AddStream(local_stream); |
| UpdateNegotiationNeeded(); |
| return true; |
| } |
| |
| void PeerConnection::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 (!IsClosed()) { |
| for (const auto& track : local_stream->GetAudioTracks()) { |
| RemoveAudioTrack(track.get(), local_stream); |
| } |
| for (const auto& track : local_stream->GetVideoTracks()) { |
| RemoveVideoTrack(track.get(), local_stream); |
| } |
| } |
| 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 (IsClosed()) { |
| return; |
| } |
| UpdateNegotiationNeeded(); |
| } |
| |
| RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>> PeerConnection::AddTrack( |
| rtc::scoped_refptr<MediaStreamTrackInterface> track, |
| const std::vector<std::string>& stream_ids) { |
| RTC_DCHECK_RUN_ON(signaling_thread()); |
| TRACE_EVENT0("webrtc", "PeerConnection::AddTrack"); |
| if (!track) { |
| LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, "Track is null."); |
| } |
| if (!(track->kind() == MediaStreamTrackInterface::kAudioKind || |
| track->kind() == MediaStreamTrackInterface::kVideoKind)) { |
| LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, |
| "Track has invalid kind: " + track->kind()); |
| } |
| if (IsClosed()) { |
| LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_STATE, |
| "PeerConnection is closed."); |
| } |
| if (FindSenderForTrack(track)) { |
| LOG_AND_RETURN_ERROR( |
| RTCErrorType::INVALID_PARAMETER, |
| "Sender already exists for track " + track->id() + "."); |
| } |
| auto sender_or_error = |
| (IsUnifiedPlan() ? AddTrackUnifiedPlan(track, stream_ids) |
| : AddTrackPlanB(track, stream_ids)); |
| if (sender_or_error.ok()) { |
| UpdateNegotiationNeeded(); |
| stats_->AddTrack(track); |
| } |
| return sender_or_error; |
| } |
| |
| RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>> |
| PeerConnection::AddTrackPlanB( |
| rtc::scoped_refptr<MediaStreamTrackInterface> track, |
| const std::vector<std::string>& stream_ids) { |
| if (stream_ids.size() > 1u) { |
| LOG_AND_RETURN_ERROR(RTCErrorType::UNSUPPORTED_OPERATION, |
| "AddTrack with more than one stream is not " |
| "supported with Plan B semantics."); |
| } |
| std::vector<std::string> adjusted_stream_ids = stream_ids; |
| if (adjusted_stream_ids.empty()) { |
| adjusted_stream_ids.push_back(rtc::CreateRandomUuid()); |
| } |
| cricket::MediaType media_type = |
| (track->kind() == MediaStreamTrackInterface::kAudioKind |
| ? cricket::MEDIA_TYPE_AUDIO |
| : cricket::MEDIA_TYPE_VIDEO); |
| auto new_sender = |
| CreateSender(media_type, track->id(), track, adjusted_stream_ids, {}); |
| if (track->kind() == MediaStreamTrackInterface::kAudioKind) { |
| new_sender->internal()->SetMediaChannel(voice_media_channel()); |
| GetAudioTransceiver()->internal()->AddSender(new_sender); |
| const RtpSenderInfo* sender_info = |
| FindSenderInfo(local_audio_sender_infos_, |
| new_sender->internal()->stream_ids()[0], track->id()); |
| if (sender_info) { |
| new_sender->internal()->SetSsrc(sender_info->first_ssrc); |
| } |
| } else { |
| RTC_DCHECK_EQ(MediaStreamTrackInterface::kVideoKind, track->kind()); |
| new_sender->internal()->SetMediaChannel(video_media_channel()); |
| GetVideoTransceiver()->internal()->AddSender(new_sender); |
| const RtpSenderInfo* sender_info = |
| FindSenderInfo(local_video_sender_infos_, |
| new_sender->internal()->stream_ids()[0], track->id()); |
| if (sender_info) { |
| new_sender->internal()->SetSsrc(sender_info->first_ssrc); |
| } |
| } |
| return rtc::scoped_refptr<RtpSenderInterface>(new_sender); |
| } |
| |
| RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>> |
| PeerConnection::AddTrackUnifiedPlan( |
| rtc::scoped_refptr<MediaStreamTrackInterface> track, |
| const std::vector<std::string>& stream_ids) { |
| auto transceiver = FindFirstTransceiverForAddedTrack(track); |
| if (transceiver) { |
| RTC_LOG(LS_INFO) << "Reusing an existing " |
| << cricket::MediaTypeToString(transceiver->media_type()) |
| << " transceiver for AddTrack."; |
| if (transceiver->direction() == RtpTransceiverDirection::kRecvOnly) { |
| transceiver->internal()->set_direction( |
| RtpTransceiverDirection::kSendRecv); |
| } else if (transceiver->direction() == RtpTransceiverDirection::kInactive) { |
| transceiver->internal()->set_direction( |
| RtpTransceiverDirection::kSendOnly); |
| } |
| transceiver->sender()->SetTrack(track); |
| transceiver->internal()->sender_internal()->set_stream_ids(stream_ids); |
| transceiver->internal()->set_reused_for_addtrack(true); |
| } else { |
| cricket::MediaType media_type = |
| (track->kind() == MediaStreamTrackInterface::kAudioKind |
| ? cricket::MEDIA_TYPE_AUDIO |
| : cricket::MEDIA_TYPE_VIDEO); |
| RTC_LOG(LS_INFO) << "Adding " << cricket::MediaTypeToString(media_type) |
| << " transceiver in response to a call to AddTrack."; |
| std::string sender_id = track->id(); |
| // Avoid creating a sender with an existing ID by generating a random ID. |
| // This can happen if this is the second time AddTrack has created a sender |
| // for this track. |
| if (FindSenderById(sender_id)) { |
| sender_id = rtc::CreateRandomUuid(); |
| } |
| auto sender = CreateSender(media_type, sender_id, track, stream_ids, {}); |
| auto receiver = CreateReceiver(media_type, rtc::CreateRandomUuid()); |
| transceiver = CreateAndAddTransceiver(sender, receiver); |
| transceiver->internal()->set_created_by_addtrack(true); |
| transceiver->internal()->set_direction(RtpTransceiverDirection::kSendRecv); |
| } |
| return transceiver->sender(); |
| } |
| |
| rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>> |
| PeerConnection::FindFirstTransceiverForAddedTrack( |
| rtc::scoped_refptr<MediaStreamTrackInterface> track) { |
| RTC_DCHECK(track); |
| for (auto transceiver : transceivers_) { |
| if (!transceiver->sender()->track() && |
| cricket::MediaTypeToString(transceiver->media_type()) == |
| track->kind() && |
| !transceiver->internal()->has_ever_been_used_to_send() && |
| !transceiver->stopped()) { |
| return transceiver; |
| } |
| } |
| return nullptr; |
| } |
| |
| bool PeerConnection::RemoveTrack(RtpSenderInterface* sender) { |
| TRACE_EVENT0("webrtc", "PeerConnection::RemoveTrack"); |
| return RemoveTrackNew(sender).ok(); |
| } |
| |
| RTCError PeerConnection::RemoveTrackNew( |
| rtc::scoped_refptr<RtpSenderInterface> sender) { |
| RTC_DCHECK_RUN_ON(signaling_thread()); |
| if (!sender) { |
| LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, "Sender is null."); |
| } |
| if (IsClosed()) { |
| LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_STATE, |
| "PeerConnection is closed."); |
| } |
| if (IsUnifiedPlan()) { |
| auto transceiver = FindTransceiverBySender(sender); |
| if (!transceiver || !sender->track()) { |
| return RTCError::OK(); |
| } |
| sender->SetTrack(nullptr); |
| if (transceiver->direction() == RtpTransceiverDirection::kSendRecv) { |
| transceiver->internal()->set_direction( |
| RtpTransceiverDirection::kRecvOnly); |
| } else if (transceiver->direction() == RtpTransceiverDirection::kSendOnly) { |
| transceiver->internal()->set_direction( |
| RtpTransceiverDirection::kInactive); |
| } |
| } else { |
| bool removed; |
| if (sender->media_type() == cricket::MEDIA_TYPE_AUDIO) { |
| removed = GetAudioTransceiver()->internal()->RemoveSender(sender); |
| } else { |
| RTC_DCHECK_EQ(cricket::MEDIA_TYPE_VIDEO, sender->media_type()); |
| removed = GetVideoTransceiver()->internal()->RemoveSender(sender); |
| } |
| if (!removed) { |
| LOG_AND_RETURN_ERROR( |
| RTCErrorType::INVALID_PARAMETER, |
| "Couldn't find sender " + sender->id() + " to remove."); |
| } |
| } |
| UpdateNegotiationNeeded(); |
| return RTCError::OK(); |
| } |
| |
| rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>> |
| PeerConnection::FindTransceiverBySender( |
| rtc::scoped_refptr<RtpSenderInterface> sender) { |
| for (auto transceiver : transceivers_) { |
| if (transceiver->sender() == sender) { |
| return transceiver; |
| } |
| } |
| return nullptr; |
| } |
| |
| RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> |
| PeerConnection::AddTransceiver( |
| rtc::scoped_refptr<MediaStreamTrackInterface> track) { |
| return AddTransceiver(track, RtpTransceiverInit()); |
| } |
| |
| RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> |
| PeerConnection::AddTransceiver( |
| rtc::scoped_refptr<MediaStreamTrackInterface> track, |
| const RtpTransceiverInit& init) { |
| RTC_DCHECK_RUN_ON(signaling_thread()); |
| RTC_CHECK(IsUnifiedPlan()) |
| << "AddTransceiver is only available with Unified Plan SdpSemantics"; |
| if (!track) { |
| LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, "track is null"); |
| } |
| cricket::MediaType media_type; |
| if (track->kind() == MediaStreamTrackInterface::kAudioKind) { |
| media_type = cricket::MEDIA_TYPE_AUDIO; |
| } else if (track->kind() == MediaStreamTrackInterface::kVideoKind) { |
| media_type = cricket::MEDIA_TYPE_VIDEO; |
| } else { |
| LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, |
| "Track kind is not audio or video"); |
| } |
| return AddTransceiver(media_type, track, init); |
| } |
| |
| RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> |
| PeerConnection::AddTransceiver(cricket::MediaType media_type) { |
| return AddTransceiver(media_type, RtpTransceiverInit()); |
| } |
| |
| RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> |
| PeerConnection::AddTransceiver(cricket::MediaType media_type, |
| const RtpTransceiverInit& init) { |
| RTC_DCHECK_RUN_ON(signaling_thread()); |
| 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); |
| } |
| |
| RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> |
| PeerConnection::AddTransceiver( |
| cricket::MediaType media_type, |
| rtc::scoped_refptr<MediaStreamTrackInterface> track, |
| const RtpTransceiverInit& init, |
| bool update_negotiation_needed) { |
| RTC_DCHECK((media_type == cricket::MEDIA_TYPE_AUDIO || |
| media_type == cricket::MEDIA_TYPE_VIDEO)); |
| if (track) { |
| RTC_DCHECK_EQ(media_type, |
| (track->kind() == MediaStreamTrackInterface::kAudioKind |
| ? cricket::MEDIA_TYPE_AUDIO |
| : cricket::MEDIA_TYPE_VIDEO)); |
| } |
| |
| RTC_HISTOGRAM_COUNTS_LINEAR(kSimulcastNumberOfEncodings, |
| init.send_encodings.size(), 0, 7, 8); |
| |
| 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."); |
| } |
| |
| RtpParameters parameters; |
| parameters.encodings = init.send_encodings; |
| |
| // Encodings are dropped from the tail if too many are provided. |
| if (parameters.encodings.size() > kMaxSimulcastStreams) { |
| parameters.encodings.erase( |
| parameters.encodings.begin() + kMaxSimulcastStreams, |
| 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(); |
| } |
| } |
| |
| if (UnimplementedRtpParameterHasValue(parameters)) { |
| LOG_AND_RETURN_ERROR( |
| RTCErrorType::UNSUPPORTED_PARAMETER, |
| "Attempted to set an unimplemented parameter of RtpParameters."); |
| } |
| |
| auto result = cricket::CheckRtpParametersValues(parameters); |
| if (!result.ok()) { |
| 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 && !FindSenderById(track->id()) ? track->id() |
| : rtc::CreateRandomUuid()); |
| auto sender = CreateSender(media_type, sender_id, track, init.stream_ids, |
| parameters.encodings); |
| auto receiver = CreateReceiver(media_type, rtc::CreateRandomUuid()); |
| auto transceiver = CreateAndAddTransceiver(sender, receiver); |
| transceiver->internal()->set_direction(init.direction); |
| |
| if (update_negotiation_needed) { |
| UpdateNegotiationNeeded(); |
| } |
| |
| return rtc::scoped_refptr<RtpTransceiverInterface>(transceiver); |
| } |
| |
| rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>> |
| PeerConnection::CreateSender( |
| cricket::MediaType media_type, |
| const std::string& id, |
| rtc::scoped_refptr<MediaStreamTrackInterface> track, |
| const std::vector<std::string>& stream_ids, |
| const std::vector<RtpEncodingParameters>& send_encodings) { |
| RTC_DCHECK_RUN_ON(signaling_thread()); |
| rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>> sender; |
| if (media_type == cricket::MEDIA_TYPE_AUDIO) { |
| RTC_DCHECK(!track || |
| (track->kind() == MediaStreamTrackInterface::kAudioKind)); |
| sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create( |
| signaling_thread(), |
| AudioRtpSender::Create(worker_thread(), id, stats_.get(), this)); |
| NoteUsageEvent(UsageEvent::AUDIO_ADDED); |
| } else { |
| RTC_DCHECK_EQ(media_type, cricket::MEDIA_TYPE_VIDEO); |
| RTC_DCHECK(!track || |
| (track->kind() == MediaStreamTrackInterface::kVideoKind)); |
| sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create( |
| signaling_thread(), VideoRtpSender::Create(worker_thread(), id, this)); |
| NoteUsageEvent(UsageEvent::VIDEO_ADDED); |
| } |
| bool set_track_succeeded = sender->SetTrack(track); |
| RTC_DCHECK(set_track_succeeded); |
| sender->internal()->set_stream_ids(stream_ids); |
| sender->internal()->set_init_send_encodings(send_encodings); |
| return sender; |
| } |
| |
| rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>> |
| PeerConnection::CreateReceiver(cricket::MediaType media_type, |
| const std::string& receiver_id) { |
| rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>> |
| receiver; |
| if (media_type == cricket::MEDIA_TYPE_AUDIO) { |
| receiver = RtpReceiverProxyWithInternal<RtpReceiverInternal>::Create( |
| signaling_thread(), new AudioRtpReceiver(worker_thread(), receiver_id, |
| std::vector<std::string>({}))); |
| NoteUsageEvent(UsageEvent::AUDIO_ADDED); |
| } else { |
| RTC_DCHECK_EQ(media_type, cricket::MEDIA_TYPE_VIDEO); |
| receiver = RtpReceiverProxyWithInternal<RtpReceiverInternal>::Create( |
| signaling_thread(), new VideoRtpReceiver(worker_thread(), receiver_id, |
| std::vector<std::string>({}))); |
| NoteUsageEvent(UsageEvent::VIDEO_ADDED); |
| } |
| return receiver; |
| } |
| |
| rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>> |
| PeerConnection::CreateAndAddTransceiver( |
| rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>> sender, |
| rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>> |
| receiver) { |
| // Ensure that the new sender does not have an ID that is already in use by |
| // another sender. |
| // Allow receiver IDs to conflict since those come from remote SDP (which |
| // could be invalid, but should not cause a crash). |
| RTC_DCHECK(!FindSenderById(sender->id())); |
| auto transceiver = RtpTransceiverProxyWithInternal<RtpTransceiver>::Create( |
| signaling_thread(), |
| new RtpTransceiver( |
| sender, receiver, channel_manager(), |
| sender->media_type() == cricket::MEDIA_TYPE_AUDIO |
| ? channel_manager()->GetSupportedAudioRtpHeaderExtensions() |
| : channel_manager()->GetSupportedVideoRtpHeaderExtensions())); |
| transceivers_.push_back(transceiver); |
| transceiver->internal()->SignalNegotiationNeeded.connect( |
| this, &PeerConnection::OnNegotiationNeeded); |
| return transceiver; |
| } |
| |
| void PeerConnection::OnNegotiationNeeded() { |
| RTC_DCHECK_RUN_ON(signaling_thread()); |
| RTC_DCHECK(!IsClosed()); |
| UpdateNegotiationNeeded(); |
| } |
| |
| rtc::scoped_refptr<RtpSenderInterface> PeerConnection::CreateSender( |
| const std::string& kind, |
| const std::string& stream_id) { |
| RTC_DCHECK_RUN_ON(signaling_thread()); |
| 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( |
| worker_thread(), rtc::CreateRandomUuid(), stats_.get(), this); |
| audio_sender->SetMediaChannel(voice_media_channel()); |
| new_sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create( |
| signaling_thread(), audio_sender); |
| GetAudioTransceiver()->internal()->AddSender(new_sender); |
| } else if (kind == MediaStreamTrackInterface::kVideoKind) { |
| auto video_sender = |
| VideoRtpSender::Create(worker_thread(), rtc::CreateRandomUuid(), this); |
| video_sender->SetMediaChannel(video_media_channel()); |
| new_sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create( |
| signaling_thread(), video_sender); |
| 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); |
| |
| return new_sender; |
| } |
| |
| std::vector<rtc::scoped_refptr<RtpSenderInterface>> PeerConnection::GetSenders() |
| const { |
| RTC_DCHECK_RUN_ON(signaling_thread()); |
| std::vector<rtc::scoped_refptr<RtpSenderInterface>> ret; |
| for (const auto& sender : GetSendersInternal()) { |
| ret.push_back(sender); |
| } |
| return ret; |
| } |
| |
| std::vector<rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>>> |
| PeerConnection::GetSendersInternal() const { |
| std::vector<rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>>> |
| all_senders; |
| for (const auto& transceiver : transceivers_) { |
| auto senders = transceiver->internal()->senders(); |
| all_senders.insert(all_senders.end(), senders.begin(), senders.end()); |
| } |
| return all_senders; |
| } |
| |
| std::vector<rtc::scoped_refptr<RtpReceiverInterface>> |
| PeerConnection::GetReceivers() const { |
| RTC_DCHECK_RUN_ON(signaling_thread()); |
| std::vector<rtc::scoped_refptr<RtpReceiverInterface>> ret; |
| for (const auto& receiver : GetReceiversInternal()) { |
| ret.push_back(receiver); |
| } |
| return ret; |
| } |
| |
| std::vector< |
| rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>>> |
| PeerConnection::GetReceiversInternal() const { |
| std::vector< |
| rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>>> |
| all_receivers; |
| for (const auto& transceiver : transceivers_) { |
| auto receivers = transceiver->internal()->receivers(); |
| all_receivers.insert(all_receivers.end(), receivers.begin(), |
| receivers.end()); |
| } |
| return all_receivers; |
| } |
| |
| std::vector<rtc::scoped_refptr<RtpTransceiverInterface>> |
| PeerConnection::GetTransceivers() const { |
| RTC_DCHECK_RUN_ON(signaling_thread()); |
| RTC_CHECK(IsUnifiedPlan()) |
| << "GetTransceivers is only supported with Unified Plan SdpSemantics."; |
| std::vector<rtc::scoped_refptr<RtpTransceiverInterface>> all_transceivers; |
| for (const auto& transceiver : transceivers_) { |
| all_transceivers.push_back(transceiver); |
| } |
| return all_transceivers; |
| } |
| |
| bool PeerConnection::GetStats(StatsObserver* observer, |
| MediaStreamTrackInterface* track, |
| StatsOutputLevel level) { |
| TRACE_EVENT0("webrtc", "PeerConnection::GetStats"); |
| RTC_DCHECK_RUN_ON(signaling_thread()); |
| if (!observer) { |
| RTC_LOG(LS_ERROR) << "GetStats - observer is NULL."; |
| return false; |
| } |
| |
| stats_->UpdateStats(level); |
| // The StatsCollector is used to tell if a track is valid because it may |
| // remember tracks that the PeerConnection previously removed. |
| if (track && !stats_->IsValidTrack(track->id())) { |
| RTC_LOG(LS_WARNING) << "GetStats is called with an invalid track: " |
| << track->id(); |
| return false; |
| } |
| signaling_thread()->Post(RTC_FROM_HERE, this, MSG_GETSTATS, |
| new GetStatsMsg(observer, track)); |
| return true; |
| } |
| |
| void PeerConnection::GetStats(RTCStatsCollectorCallback* callback) { |
| TRACE_EVENT0("webrtc", "PeerConnection::GetStats"); |
| RTC_DCHECK_RUN_ON(signaling_thread()); |
| RTC_DCHECK(stats_collector_); |
| RTC_DCHECK(callback); |
| stats_collector_->GetStatsReport(callback); |
| } |
| |
| 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::scoped_refptr<RtpSenderInternal> internal_sender; |
| if (selector) { |
| for (const auto& proxy_transceiver : transceivers_) { |
| for (const auto& 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); |
| } |
| |
| 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::scoped_refptr<RtpReceiverInternal> internal_receiver; |
| if (selector) { |
| for (const auto& proxy_transceiver : transceivers_) { |
| for (const auto& 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); |
| } |
| |
| PeerConnectionInterface::SignalingState PeerConnection::signaling_state() { |
| RTC_DCHECK_RUN_ON(signaling_thread()); |
| return signaling_state_; |
| } |
| |
| PeerConnectionInterface::IceConnectionState |
| PeerConnection::ice_connection_state() { |
| RTC_DCHECK_RUN_ON(signaling_thread()); |
| return ice_connection_state_; |
| } |
| |
| PeerConnectionInterface::IceConnectionState |
| PeerConnection::standardized_ice_connection_state() { |
| RTC_DCHECK_RUN_ON(signaling_thread()); |
| return standardized_ice_connection_state_; |
| } |
| |
| PeerConnectionInterface::PeerConnectionState |
| PeerConnection::peer_connection_state() { |
| RTC_DCHECK_RUN_ON(signaling_thread()); |
| return connection_state_; |
| } |
| |
| PeerConnectionInterface::IceGatheringState |
| PeerConnection::ice_gathering_state() { |
| RTC_DCHECK_RUN_ON(signaling_thread()); |
| return ice_gathering_state_; |
| } |
| |
| absl::optional<bool> PeerConnection::can_trickle_ice_candidates() { |
| RTC_DCHECK_RUN_ON(signaling_thread()); |
| SessionDescriptionInterface* description = current_remote_description_.get(); |
| if (!description) { |
| description = pending_remote_description_.get(); |
| } |
| if (!description) { |
| return absl::nullopt; |
| } |
| // TODO(bugs.webrtc.org/7443): Change to retrieve from session-level option. |
| if (description->description()->transport_infos().size() < 1) { |
| return absl::nullopt; |
| } |
| return description->description()->transport_infos()[0].description.HasOption( |
| "trickle"); |
| } |
| |
| rtc::scoped_refptr<DataChannelInterface> PeerConnection::CreateDataChannel( |
| const std::string& label, |
| const DataChannelInit* config) { |
| RTC_DCHECK_RUN_ON(signaling_thread()); |
| TRACE_EVENT0("webrtc", "PeerConnection::CreateDataChannel"); |
| |
| bool first_datachannel = !data_channel_controller_.HasDataChannels(); |
| |
| std::unique_ptr<InternalDataChannelInit> internal_config; |
| if (config) { |
| internal_config.reset(new InternalDataChannelInit(*config)); |
| } |
| rtc::scoped_refptr<DataChannelInterface> channel( |
| data_channel_controller_.InternalCreateDataChannel( |
| label, internal_config.get())); |
| if (!channel.get()) { |
| return nullptr; |
| } |
| |
| // Trigger the onRenegotiationNeeded event for every new RTP DataChannel, or |
| // the first SCTP DataChannel. |
| if (data_channel_type() == cricket::DCT_RTP || first_datachannel) { |
| UpdateNegotiationNeeded(); |
| } |
| NoteUsageEvent(UsageEvent::DATA_ADDED); |
| return DataChannelProxy::Create(signaling_thread(), channel.get()); |
| } |
| |
| void PeerConnection::RestartIce() { |
| RTC_DCHECK_RUN_ON(signaling_thread()); |
| local_ice_credentials_to_replace_->SetIceCredentialsFromLocalDescriptions( |
| current_local_description_.get(), pending_local_description_.get()); |
| UpdateNegotiationNeeded(); |
| } |
| |
| void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer, |
| const 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) { |
| // Abort early if |this_weak_ptr| is no longer valid. |
| 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. |
| rtc::scoped_refptr<CreateSessionDescriptionObserverOperationWrapper> |
| observer_wrapper(new rtc::RefCountedObject< |
| CreateSessionDescriptionObserverOperationWrapper>( |
| std::move(observer_refptr), |
| std::move(operations_chain_callback))); |
| this_weak_ptr->DoCreateOffer(options, observer_wrapper); |
| }); |
| } |
| |
| void PeerConnection::DoCreateOffer( |
| const RTCOfferAnswerOptions& options, |
| rtc::scoped_refptr<CreateSessionDescriptionObserver> observer) { |
| RTC_DCHECK_RUN_ON(signaling_thread()); |
| TRACE_EVENT0("webrtc", "PeerConnection::DoCreateOffer"); |
| |
| if (!observer) { |
| RTC_LOG(LS_ERROR) << "CreateOffer - observer is NULL."; |
| return; |
| } |
| |
| if (IsClosed()) { |
| std::string error = "CreateOffer called when PeerConnection is closed."; |
| RTC_LOG(LS_ERROR) << error; |
| PostCreateSessionDescriptionFailure( |
| observer, RTCError(RTCErrorType::INVALID_STATE, std::move(error))); |
| return; |
| } |
| |
| // If a session error has occurred the PeerConnection is in a possibly |
| // inconsistent state so fail right away. |
| if (session_error() != SessionError::kNone) { |
| std::string error_message = GetSessionErrorMsg(); |
| RTC_LOG(LS_ERROR) << "CreateOffer: " << error_message; |
| PostCreateSessionDescriptionFailure( |
| observer, |
| 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; |
| PostCreateSessionDescriptionFailure( |
| observer, RTCError(RTCErrorType::INVALID_PARAMETER, std::move(error))); |
| 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()) { |
| RTCError error = HandleLegacyOfferOptions(options); |
| if (!error.ok()) { |
| PostCreateSessionDescriptionFailure(observer, std::move(error)); |
| return; |
| } |
| } |
| |
| cricket::MediaSessionOptions session_options; |
| GetOptionsForOffer(options, &session_options); |
| webrtc_session_desc_factory_->CreateOffer(observer, options, session_options); |
| } |
| |
| RTCError PeerConnection::HandleLegacyOfferOptions( |
| const RTCOfferAnswerOptions& options) { |
| 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 PeerConnection::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 PeerConnection::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; |
| AddTransceiver(media_type, nullptr, init, |
| /*update_negotiation_needed=*/false); |
| } |
| } |
| |
| std::vector<rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>> |
| PeerConnection::GetReceivingTransceiversOfType(cricket::MediaType media_type) { |
| std::vector< |
| rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>> |
| receiving_transceivers; |
| for (const auto& transceiver : transceivers_) { |
| if (!transceiver->stopped() && transceiver->media_type() == media_type && |
| RtpTransceiverDirectionHasRecv(transceiver->direction())) { |
| receiving_transceivers.push_back(transceiver); |
| } |
| } |
| return receiving_transceivers; |
| } |
| |
| void PeerConnection::CreateAnswer(CreateSessionDescriptionObserver* observer, |
| const 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) { |
| // Abort early if |this_weak_ptr| is no longer valid. |
| 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. |
| rtc::scoped_refptr<CreateSessionDescriptionObserverOperationWrapper> |
| observer_wrapper(new rtc::RefCountedObject< |
| CreateSessionDescriptionObserverOperationWrapper>( |
| std::move(observer_refptr), |
| std::move(operations_chain_callback))); |
| this_weak_ptr->DoCreateAnswer(options, observer_wrapper); |
| }); |
| } |
| |
| void PeerConnection::DoCreateAnswer( |
| const RTCOfferAnswerOptions& options, |
| rtc::scoped_refptr<CreateSessionDescriptionObserver> observer) { |
| RTC_DCHECK_RUN_ON(signaling_thread()); |
| TRACE_EVENT0("webrtc", "PeerConnection::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. |
| if (session_error() != SessionError::kNone) { |
| std::string error_message = GetSessionErrorMsg(); |
| RTC_LOG(LS_ERROR) << "CreateAnswer: " << error_message; |
| PostCreateSessionDescriptionFailure( |
| observer, |
| RTCError(RTCErrorType::INTERNAL_ERROR, std::move(error_message))); |
| return; |
| } |
| |
| if (!(signaling_state_ == kHaveRemoteOffer || |
| signaling_state_ == 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; |
| PostCreateSessionDescriptionFailure( |
| observer, RTCError(RTCErrorType::INVALID_STATE, std::move(error))); |
| 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 != 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 != 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; |
| GetOptionsForAnswer(options, &session_options); |
| |
| webrtc_session_desc_factory_->CreateAnswer(observer, session_options); |
| } |
| |
| void PeerConnection::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 { |
| // Abort early if |this_weak_ptr| is no longer valid. |
| if (!this_weak_ptr) { |
| // For consistency with DoSetLocalDescription(), we DO NOT inform the |
| // |observer_refptr| that the operation failed in this case. |
| // TODO(hbos): If/when we process SLD messages in ~PeerConnection, |
| // the consistent thing would be to inform the observer here. |
| operations_chain_callback(); |
| return; |
| } |
| this_weak_ptr->DoSetLocalDescription(std::move(desc), |
| std::move(observer_refptr)); |
| // DoSetLocalDescription() is currently implemented as a synchronous |
| // operation but where the |observer|'s callbacks are invoked |
| // asynchronously in a post to OnMessage(). |
| // For backwards-compatability reasons, we declare the operation as |
| // completed here (rather than in OnMessage()). This ensures that |
| // subsequent offer/answer operations can start immediately (without |
| // waiting for OnMessage()). |
| operations_chain_callback(); |
| }); |
| } |
| |
| void PeerConnection::SetLocalDescription( |
| SetSessionDescriptionObserver* observer) { |
| RTC_DCHECK_RUN_ON(signaling_thread()); |
| // The |create_sdp_observer| handles performing DoSetLocalDescription() with |
| // the resulting description as well as completing the operation. |
| rtc::scoped_refptr<ImplicitCreateSessionDescriptionObserver> |
| create_sdp_observer( |
| new rtc::RefCountedObject<ImplicitCreateSessionDescriptionObserver>( |
| weak_ptr_factory_.GetWeakPtr(), |
| rtc::scoped_refptr<SetSessionDescriptionObserver>(observer))); |
| // 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) { |
| // The |create_sdp_observer| is responsible for completing the |
| // operation. |
| create_sdp_observer->SetOperationCompleteCallback( |
| std::move(operations_chain_callback)); |
| // Abort early if |this_weak_ptr| is no longer valid. This triggers the |
| // 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(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(RTCOfferAnswerOptions(), |
| create_sdp_observer); |
| break; |
| case PeerConnectionInterface::kClosed: |
| create_sdp_observer->OnFailure(RTCError( |
| RTCErrorType::INVALID_STATE, |
| "SetLocalDescription called when PeerConnection is closed.")); |
| break; |
| } |
| }); |
| } |
| |
| void PeerConnection::DoSetLocalDescription( |
| std::unique_ptr<SessionDescriptionInterface> desc, |
| rtc::scoped_refptr<SetSessionDescriptionObserver> observer) { |
| RTC_DCHECK_RUN_ON(signaling_thread()); |
| TRACE_EVENT0("webrtc", "PeerConnection::DoSetLocalDescription"); |
| |
| if (!observer) { |
| RTC_LOG(LS_ERROR) << "SetLocalDescription - observer is NULL."; |
| return; |
| } |
| |
| if (!desc) { |
| PostSetSessionDescriptionFailure( |
| observer, |
| 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. |
| if (session_error() != SessionError::kNone) { |
| std::string error_message = GetSessionErrorMsg(); |
| RTC_LOG(LS_ERROR) << "SetLocalDescription: " << error_message; |
| PostSetSessionDescriptionFailure( |
| observer, |
| RTCError(RTCErrorType::INTERNAL_ERROR, std::move(error_message))); |
| return; |
| } |
| |
| // For SLD we support only explicit rollback. |
| if (desc->GetType() == SdpType::kRollback) { |
| if (IsUnifiedPlan()) { |
| RTCError error = Rollback(desc->GetType()); |
| if (error.ok()) { |
| PostSetSessionDescriptionSuccess(observer); |
| } else { |
| PostSetSessionDescriptionFailure(observer, std::move(error)); |
| } |
| } else { |
| PostSetSessionDescriptionFailure( |
| observer, RTCError(RTCErrorType::UNSUPPORTED_OPERATION, |
| "Rollback not supported in Plan B")); |
| } |
| return; |
| } |
| |
| RTCError error = ValidateSessionDescription(desc.get(), cricket::CS_LOCAL); |
| if (!error.ok()) { |
| std::string error_message = GetSetDescriptionErrorMessage( |
| cricket::CS_LOCAL, desc->GetType(), error); |
| RTC_LOG(LS_ERROR) << error_message; |
| PostSetSessionDescriptionFailure( |
| observer, |
| RTCError(RTCErrorType::INTERNAL_ERROR, std::move(error_message))); |
| return; |
| } |
| |
| // Grab the description type before moving ownership to ApplyLocalDescription, |
| // which may destroy it before returning. |
| const SdpType type = desc->GetType(); |
| |
| error = ApplyLocalDescription(std::move(desc)); |
| // |desc| may be destroyed at this point. |
|