| /* |
| * 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/peerconnection.h" |
| |
| #include <algorithm> |
| #include <limits> |
| #include <queue> |
| #include <set> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/memory/memory.h" |
| #include "api/jsepicecandidate.h" |
| #include "api/jsepsessiondescription.h" |
| #include "api/mediaconstraintsinterface.h" |
| #include "api/mediastreamproxy.h" |
| #include "api/mediastreamtrackproxy.h" |
| #include "call/call.h" |
| #include "logging/rtc_event_log/icelogger.h" |
| #include "logging/rtc_event_log/output/rtc_event_log_output_file.h" |
| #include "logging/rtc_event_log/rtc_event_log.h" |
| #include "media/sctp/sctptransport.h" |
| #include "pc/audiotrack.h" |
| #include "pc/channel.h" |
| #include "pc/channelmanager.h" |
| #include "pc/dtmfsender.h" |
| #include "pc/mediastream.h" |
| #include "pc/mediastreamobserver.h" |
| #include "pc/remoteaudiosource.h" |
| #include "pc/rtpmediautils.h" |
| #include "pc/rtpreceiver.h" |
| #include "pc/rtpsender.h" |
| #include "pc/sctputils.h" |
| #include "pc/sdputils.h" |
| #include "pc/streamcollection.h" |
| #include "pc/videocapturertracksource.h" |
| #include "pc/videotrack.h" |
| #include "rtc_base/bind.h" |
| #include "rtc_base/checks.h" |
| #include "rtc_base/logging.h" |
| #include "rtc_base/numerics/safe_conversions.h" |
| #include "rtc_base/stringencode.h" |
| #include "rtc_base/stringutils.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::SessionDescription; |
| using cricket::MediaProtocolType; |
| using cricket::TransportInfo; |
| |
| using cricket::LOCAL_PORT_TYPE; |
| using cricket::STUN_PORT_TYPE; |
| using cricket::RELAY_PORT_TYPE; |
| using cricket::PRFLX_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 { |
| |
| 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_FREE_DATACHANNELS, |
| 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 AddRtpSenderOptions( |
| const std::vector<rtc::scoped_refptr< |
| RtpSenderProxyWithInternal<RtpSenderInternal>>>& senders, |
| cricket::MediaDescriptionOptions* audio_media_description_options, |
| cricket::MediaDescriptionOptions* video_media_description_options) { |
| 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(), 1); |
| } |
| } |
| } |
| } |
| |
| // 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; |
| } |
| |
| // Helper to set an error and return from a method. |
| bool SafeSetError(webrtc::RTCErrorType type, webrtc::RTCError* error) { |
| if (error) { |
| error->set_type(type); |
| } |
| return type == webrtc::RTCErrorType::NONE; |
| } |
| |
| bool SafeSetError(webrtc::RTCError error, webrtc::RTCError* error_out) { |
| bool ok = error.ok(); |
| if (error_out) { |
| *error_out = std::move(error); |
| } |
| return ok; |
| } |
| |
| 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_private = IPIsPrivate(local.address().ipaddr()); |
| bool remote_private = IPIsPrivate(remote.address().ipaddr()); |
| if (local_private) { |
| if (remote_private) { |
| return kIceCandidatePairHostPrivateHostPrivate; |
| } else { |
| return kIceCandidatePairHostPrivateHostPublic; |
| } |
| } 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) { |
| RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.KeyProtocol", protocol_type, |
| kEnumCounterKeyProtocolMax); |
| static const std::map<std::pair<KeyExchangeProtocolType, cricket::MediaType>, |
| KeyExchangeProtocolMedia> |
| proto_media_counter_map = { |
| {{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}}; |
| |
| auto it = proto_media_counter_map.find({protocol_type, media_type}); |
| if (it != proto_media_counter_map.end()) { |
| RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.KeyProtocolByMedia", |
| it->second, 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; |
| } |
| |
| bool GetTrackIdBySsrc(const SessionDescription* session_description, |
| uint32_t ssrc, |
| std::string* track_id) { |
| RTC_DCHECK(track_id != NULL); |
| |
| const cricket::AudioContentDescription* audio_desc = |
| cricket::GetFirstAudioContentDescription(session_description); |
| if (audio_desc) { |
| const auto* found = cricket::GetStreamBySsrc(audio_desc->streams(), ssrc); |
| if (found) { |
| *track_id = found->id; |
| return true; |
| } |
| } |
| |
| const cricket::VideoContentDescription* video_desc = |
| cricket::GetFirstVideoContentDescription(session_description); |
| if (video_desc) { |
| const auto* found = cricket::GetStreamBySsrc(video_desc->streams(), ssrc); |
| if (found) { |
| *track_id = found->id; |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // Get the SCTP port out of a SessionDescription. |
| // Return -1 if not found. |
| int GetSctpPort(const SessionDescription* session_description) { |
| const cricket::DataContentDescription* data_desc = |
| GetFirstDataContentDescription(session_description); |
| RTC_DCHECK(data_desc); |
| if (!data_desc) { |
| return -1; |
| } |
| std::string value; |
| cricket::DataCodec match_pattern(cricket::kGoogleSctpDataCodecPlType, |
| cricket::kGoogleSctpDataCodecName); |
| for (const cricket::DataCodec& codec : data_desc->codecs()) { |
| if (!codec.Matches(match_pattern)) { |
| continue; |
| } |
| if (codec.GetParam(cricket::kCodecParamPort, &value)) { |
| return rtc::FromString<int>(value); |
| } |
| } |
| return -1; |
| } |
| |
| // 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) { |
| std::ostringstream oss; |
| oss << "Failed to set " << (source == cricket::CS_LOCAL ? "local" : "remote") |
| << " " << SdpTypeToString(type) << " sdp: " << error.message(); |
| return oss.str(); |
| } |
| |
| 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; |
| } |
| |
| } // namespace |
| |
| // 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 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; |
| bool presume_writable_when_fully_relayed; |
| bool enable_ice_renomination; |
| bool redetermine_role_on_ice_restart; |
| 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> stun_candidate_keepalive_interval; |
| absl::optional<rtc::IntervalRange> ice_regather_interval_range; |
| webrtc::TurnCustomizer* turn_customizer; |
| SdpSemantics sdp_semantics; |
| absl::optional<rtc::AdapterType> network_preference; |
| bool active_reset_srtp_params; |
| }; |
| 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 && |
| 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 && |
| 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 && |
| 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 && |
| stun_candidate_keepalive_interval == |
| o.stun_candidate_keepalive_interval && |
| ice_regather_interval_range == o.ice_regather_interval_range && |
| turn_customizer == o.turn_customizer && |
| sdp_semantics == o.sdp_semantics && |
| network_preference == o.network_preference && |
| active_reset_srtp_params == o.active_reset_srtp_params; |
| } |
| |
| bool PeerConnectionInterface::RTCConfiguration::operator!=( |
| const PeerConnectionInterface::RTCConfiguration& o) const { |
| return !(*this == o); |
| } |
| |
| // 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; |
| } |
| |
| PeerConnection::PeerConnection(PeerConnectionFactory* factory, |
| std::unique_ptr<RtcEventLog> event_log, |
| std::unique_ptr<Call> call) |
| : factory_(factory), |
| event_log_(std::move(event_log)), |
| rtcp_cname_(GenerateRtcpCname()), |
| local_streams_(StreamCollection::Create()), |
| remote_streams_(StreamCollection::Create()), |
| call_(std::move(call)) {} |
| |
| PeerConnection::~PeerConnection() { |
| TRACE_EVENT0("webrtc", "PeerConnection::~PeerConnection"); |
| RTC_DCHECK_RUN_ON(signaling_thread()); |
| |
| // Need to stop transceivers before destroying the stats collector because |
| // AudioRtpSender has a reference to the StatsCollector it will update when |
| // stopping. |
| for (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_invoker_.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] { port_allocator_.reset(); }); |
| // call_ and event_log_ must be destroyed on the worker thread. |
| worker_thread()->Invoke<void>(RTC_FROM_HERE, [this] { |
| call_.reset(); |
| // The event log must outlive call (and any other object that uses it). |
| event_log_.reset(); |
| }); |
| } |
| |
| void PeerConnection::DestroyAllChannels() { |
| // Destroy video channels first since they may have a pointer to a voice |
| // channel. |
| for (auto transceiver : transceivers_) { |
| if (transceiver->media_type() == cricket::MEDIA_TYPE_VIDEO) { |
| DestroyTransceiverChannel(transceiver); |
| } |
| } |
| for (auto transceiver : transceivers_) { |
| if (transceiver->media_type() == cricket::MEDIA_TYPE_AUDIO) { |
| DestroyTransceiverChannel(transceiver); |
| } |
| } |
| DestroyDataChannel(); |
| } |
| |
| bool PeerConnection::Initialize( |
| const PeerConnectionInterface::RTCConfiguration& configuration, |
| PeerConnectionDependencies dependencies) { |
| 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); |
| 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; |
| } |
| |
| // The port allocator lives on the network thread and should be initialized |
| // there. |
| if (!network_thread()->Invoke<bool>( |
| RTC_FROM_HERE, |
| rtc::Bind(&PeerConnection::InitializePortAllocator_n, this, |
| stun_servers, turn_servers, configuration))) { |
| return false; |
| } |
| // 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 (port_allocator_flags_ & cricket::PORTALLOCATOR_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; |
| config.crypto_options = options.crypto_options; |
| config.transport_observer = this; |
| config.event_log = event_log_.get(); |
| #if defined(ENABLE_EXTERNAL_AUTH) |
| config.enable_external_auth = true; |
| #endif |
| config.active_reset_srtp_params = configuration.active_reset_srtp_params; |
| 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_->SignalIceGatheringState.connect( |
| this, &PeerConnection::OnTransportControllerGatheringState); |
| transport_controller_->SignalIceCandidatesGathered.connect( |
| this, &PeerConnection::OnTransportControllerCandidatesGathered); |
| transport_controller_->SignalIceCandidatesRemoved.connect( |
| this, &PeerConnection::OnTransportControllerCandidatesRemoved); |
| transport_controller_->SignalDtlsHandshakeError.connect( |
| this, &PeerConnection::OnTransportControllerDtlsHandshakeError); |
| |
| sctp_factory_ = factory_->CreateSctpTransportInternalFactory(); |
| |
| stats_.reset(new StatsCollector(this)); |
| stats_collector_ = RTCStatsCollector::Create(this); |
| |
| configuration_ = configuration; |
| |
| // 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]; |
| } |
| |
| transport_controller_->SetIceConfig(ParseIceConfig(configuration)); |
| |
| 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); |
| } |
| } |
| |
| // Enable creation of RTP data channels if the kEnableRtpDataChannels is set. |
| // It takes precendence over the disable_sctp_data_channels |
| // PeerConnectionFactoryInterface::Options. |
| if (configuration.enable_rtp_data_channel) { |
| 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_type_ = cricket::DCT_SCTP; |
| } |
| } |
| |
| 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; |
| |
| // 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)); |
| 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( |
| options.crypto_options.enable_encrypted_rtp_header_extensions); |
| |
| // 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); |
| return true; |
| } |
| |
| RTCError PeerConnection::ValidateConfiguration( |
| const RTCConfiguration& config) const { |
| if (config.ice_regather_interval_range && |
| config.continual_gathering_policy == GATHER_ONCE) { |
| return RTCError(RTCErrorType::INVALID_PARAMETER, |
| "ice_regather_interval_range specified but continual " |
| "gathering policy is GATHER_ONCE"); |
| } |
| auto result = |
| cricket::P2PTransportChannel::ValidateIceConfig(ParseIceConfig(config)); |
| return result; |
| } |
| |
| rtc::scoped_refptr<StreamCollectionInterface> PeerConnection::local_streams() { |
| 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_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_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); |
| Observer()->OnRenegotiationNeeded(); |
| return true; |
| } |
| |
| void PeerConnection::RemoveStream(MediaStreamInterface* local_stream) { |
| 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; |
| } |
| Observer()->OnRenegotiationNeeded(); |
| } |
| |
| RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>> PeerConnection::AddTrack( |
| rtc::scoped_refptr<MediaStreamTrackInterface> track, |
| const std::vector<std::string>& stream_ids) { |
| 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()) { |
| Observer()->OnRenegotiationNeeded(); |
| 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()->SetVoiceMediaChannel(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()->SetVideoMediaChannel(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); |
| } 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) { |
| 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."); |
| } |
| } |
| Observer()->OnRenegotiationNeeded(); |
| 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_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_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 fire_callback) { |
| 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)); |
| } |
| |
| // TODO(bugs.webrtc.org/7600): Verify init. |
| |
| 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); |
| auto receiver = CreateReceiver(media_type, rtc::CreateRandomUuid()); |
| auto transceiver = CreateAndAddTransceiver(sender, receiver); |
| transceiver->internal()->set_direction(init.direction); |
| |
| if (fire_callback) { |
| Observer()->OnRenegotiationNeeded(); |
| } |
| |
| 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) { |
| 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(), |
| new AudioRtpSender(worker_thread(), id, stats_.get())); |
| 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(), new VideoRtpSender(worker_thread(), id)); |
| NoteUsageEvent(UsageEvent::VIDEO_ADDED); |
| } |
| bool set_track_succeeded = sender->SetTrack(track); |
| RTC_DCHECK(set_track_succeeded); |
| sender->internal()->set_stream_ids(stream_ids); |
| 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)); |
| 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()); |
| Observer()->OnRenegotiationNeeded(); |
| } |
| |
| rtc::scoped_refptr<RtpSenderInterface> PeerConnection::CreateSender( |
| const std::string& kind, |
| const std::string& stream_id) { |
| 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 = new AudioRtpSender( |
| worker_thread(), rtc::CreateRandomUuid(), stats_.get()); |
| audio_sender->SetVoiceMediaChannel(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 = |
| new VideoRtpSender(worker_thread(), rtc::CreateRandomUuid()); |
| video_sender->SetVideoMediaChannel(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 { |
| std::vector<rtc::scoped_refptr<RtpSenderInterface>> ret; |
| for (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 (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 { |
| 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 (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_CHECK(IsUnifiedPlan()) |
| << "GetTransceivers is only supported with Unified Plan SdpSemantics."; |
| std::vector<rtc::scoped_refptr<RtpTransceiverInterface>> all_transceivers; |
| for (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(signaling_thread()->IsCurrent()); |
| 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(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(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(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() { |
| return signaling_state_; |
| } |
| |
| PeerConnectionInterface::IceConnectionState |
| PeerConnection::ice_connection_state() { |
| return ice_connection_state_; |
| } |
| |
| PeerConnectionInterface::IceGatheringState |
| PeerConnection::ice_gathering_state() { |
| return ice_gathering_state_; |
| } |
| |
| rtc::scoped_refptr<DataChannelInterface> PeerConnection::CreateDataChannel( |
| const std::string& label, |
| const DataChannelInit* config) { |
| TRACE_EVENT0("webrtc", "PeerConnection::CreateDataChannel"); |
| |
| bool first_datachannel = !HasDataChannels(); |
| |
| std::unique_ptr<InternalDataChannelInit> internal_config; |
| if (config) { |
| internal_config.reset(new InternalDataChannelInit(*config)); |
| } |
| rtc::scoped_refptr<DataChannelInterface> channel( |
| 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) { |
| Observer()->OnRenegotiationNeeded(); |
| } |
| NoteUsageEvent(UsageEvent::DATA_ADDED); |
| return DataChannelProxy::Create(signaling_thread(), channel.get()); |
| } |
| |
| void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer, |
| const RTCOfferAnswerOptions& options) { |
| TRACE_EVENT0("webrtc", "PeerConnection::CreateOffer"); |
| |
| 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 (!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 (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) { |
| 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, /*fire_callback=*/false); |
| } |
| } |
| |
| std::vector<rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>> |
| PeerConnection::GetReceivingTransceiversOfType(cricket::MediaType media_type) { |
| std::vector< |
| rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>> |
| receiving_transceivers; |
| for (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) { |
| TRACE_EVENT0("webrtc", "PeerConnection::CreateAnswer"); |
| if (!observer) { |
| RTC_LOG(LS_ERROR) << "CreateAnswer - observer is NULL."; |
| 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) { |
| TRACE_EVENT0("webrtc", "PeerConnection::SetLocalDescription"); |
| |
| // The SetLocalDescription contract is that we take ownership of the session |
| // description regardless of the outcome, so wrap it in a unique_ptr right |
| // away. Ideally, SetLocalDescription's signature will be changed to take the |
| // description as a unique_ptr argument to formalize this agreement. |
| std::unique_ptr<SessionDescriptionInterface> desc(desc_ptr); |
| |
| 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; |
| } |
| |
| 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. |
| |
| if (!error.ok()) { |
| // If ApplyLocalDescription fails, the PeerConnection could be in an |
| // inconsistent state, so act conservatively here and set the session error |
| // so that future calls to SetLocalDescription/SetRemoteDescription fail. |
| SetSessionError(SessionError::kContent, error.message()); |
| std::string error_message = |
| GetSetDescriptionErrorMessage(cricket::CS_LOCAL, type, error); |
| RTC_LOG(LS_ERROR) << error_message; |
| PostSetSessionDescriptionFailure( |
| observer, |
| RTCError(RTCErrorType::INTERNAL_ERROR, std::move(error_message))); |
| return; |
| } |
| RTC_DCHECK(local_description()); |
| |
| PostSetSessionDescriptionSuccess(observer); |
| |
| // MaybeStartGathering needs to be called after posting |
| // MSG_SET_SESSIONDESCRIPTION_SUCCESS, so that we don't signal any candidates |
| // before signaling that SetLocalDescription completed. |
| transport_controller_->MaybeStartGathering(); |
| |
| if (local_description()->GetType() == SdpType::kAnswer) { |
| // TODO(deadbeef): We already had to hop to the network thread for |
| // MaybeStartGathering... |
| network_thread()->Invoke<void>( |
| RTC_FROM_HERE, rtc::Bind(&cricket::PortAllocator::DiscardCandidatePool, |
| port_allocator_.get())); |
| // Make UMA notes about what was agreed to. |
| ReportNegotiatedSdpSemantics(*local_description()); |
| } |
| NoteUsageEvent(UsageEvent::SET_LOCAL_DESCRIPTION_CALLED); |
| } |
| |
| RTCError PeerConnection::ApplyLocalDescription( |
| std::unique_ptr<SessionDescriptionInterface> desc) { |
| RTC_DCHECK_RUN_ON(signaling_thread()); |
| RTC_DCHECK(desc); |
| |
| // Update stats here so that we have the most recent stats for tracks and |
| // streams that might be removed by updating the session description. |
| stats_->UpdateStats(kStatsOutputLevelStandard); |
| |
| // Take a reference to the old local description since it's used below to |
| // compare against the new local description. When setting the new local |
| // description, grab ownership of the replaced session description in case it |
| // is the same as |old_local_description|, to keep it alive for the duration |
| // of the method. |
| const SessionDescriptionInterface* old_local_description = |
| local_description(); |
| std::unique_ptr<SessionDescriptionInterface> replaced_local_description; |
| SdpType type = desc->GetType(); |
| if (type == SdpType::kAnswer) { |
| replaced_local_description = pending_local_description_ |
| ? std::move(pending_local_description_) |
| : std::move(current_local_description_); |
| current_local_description_ = std::move(desc); |
| pending_local_description_ = nullptr; |
| current_remote_description_ = std::move(pending_remote_description_); |
| } else { |
| replaced_local_description = std::move(pending_local_description_); |
| pending_local_description_ = std::move(desc); |
| } |
| // The session description to apply now must be accessed by |
| // |local_description()|. |
| RTC_DCHECK(local_description()); |
| |
| RTCError error = PushdownTransportDescription(cricket::CS_LOCAL, type); |
| if (!error.ok()) { |
| return error; |
| } |
| |
| if (IsUnifiedPlan()) { |
| RTCError error = UpdateTransceiversAndDataChannels( |
| cricket::CS_LOCAL, *local_description(), old_local_description, |
| remote_description()); |
| if (!error.ok()) { |
| return error; |
| } |
| std::vector<rtc::scoped_refptr<RtpTransceiverInterface>> remove_list; |
| std::vector<rtc::scoped_refptr<MediaStreamInterface>> removed_streams; |
| for (auto transceiver : transceivers_) { |
| const ContentInfo* content = |
| FindMediaSectionForTransceiver(transceiver, local_description()); |
| if (!content) { |
| continue; |
| } |
| const MediaContentDescription* media_desc = content->media_description(); |
| // 2.2.7.1.6: If description is of type "answer" or "pranswer", then run |
| // the following steps: |
| if (type == SdpType::kPrAnswer || type == SdpType::kAnswer) { |
| // 2.2.7.1.6.1: If direction is "sendonly" or "inactive", and |
| // transceiver's [[FiredDirection]] slot is either "sendrecv" or |
| // "recvonly", process the removal of a remote track for the media |
| // description, given transceiver, removeList, and muteTracks. |
| if (!RtpTransceiverDirectionHasRecv(media_desc->direction()) && |
| (transceiver->internal()->fired_direction() && |
| RtpTransceiverDirectionHasRecv( |
| *transceiver->internal()->fired_direction()))) { |
| ProcessRemovalOfRemoteTrack(transceiver, &remove_list, |
| &removed_streams); |
| } |
| // 2.2.7.1.6.2: Set transceiver's [[CurrentDirection]] and |
| // [[FiredDirection]] slots to direction. |
| transceiver->internal()->set_current_direction(media_desc->direction()); |
| transceiver->internal()->set_fired_direction(media_desc->direction()); |
| } |
| } |
| auto observer = Observer(); |
| for (auto transceiver : remove_list) { |
| observer->OnRemoveTrack(transceiver->receiver()); |
| } |
| for (auto stream : removed_streams) { |
| observer->OnRemoveStream(stream); |
| } |
| } else { |
| // Media channels will be created only when offer is set. These may use new |
| // transports just created by PushdownTransportDescription. |
| if (type == SdpType::kOffer) { |
| // TODO(bugs.webrtc.org/4676) - Handle CreateChannel failure, as new local |
| // description is applied. Restore back to old description. |
| RTCError error = CreateChannels(*local_description()->description()); |
| if (!error.ok()) { |
| return error; |
| } |
| } |
| // Remove unused channels if MediaContentDescription is rejected. |
| RemoveUnusedChannels(local_description()->description()); |
| } |
| |
| error = UpdateSessionState(type, cricket::CS_LOCAL, |
| local_description()->description()); |
| if (!error.ok()) { |
| return error; |
| } |
| |
| if (remote_description()) { |
| // Now that we have a local description, we can push down remote candidates. |
| UseCandidatesInSessionDescription(remote_description()); |
| } |
| |
| pending_ice_restarts_.clear(); |
| if (session_error() != SessionError::kNone) { |
| LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR, GetSessionErrorMsg()); |
| } |
| |
| // If setting the description decided our SSL role, allocate any necessary |
| // SCTP sids. |
| rtc::SSLRole role; |
| if (data_channel_type() == cricket::DCT_SCTP && GetSctpSslRole(&role)) { |
| AllocateSctpSids(role); |
| } |
| |
| if (IsUnifiedPlan()) { |
| for (auto transceiver : transceivers_) { |
| const ContentInfo* content = |
| FindMediaSectionForTransceiver(transceiver, local_description()); |
| if (!content) { |
| continue; |
| } |
| const auto& streams = content->media_description()->streams(); |
| if (!content->rejected && !streams.empty()) { |
| transceiver->internal()->sender_internal()->set_stream_ids( |
| streams[0].stream_ids()); |
| transceiver->internal()->sender_internal()->SetSsrc( |
| streams[0].first_ssrc()); |
| } else { |
| // 0 is a special value meaning "this sender has no associated send |
| // stream". Need to call this so the sender won't attempt to configure |
| // a no longer existing stream and run into DCHECKs in the lower |
| // layers. |
| transceiver->internal()->sender_internal()->SetSsrc(0); |
| } |
| } |
| } else { |
| // Plan B semantics. |
| |
| // Update state and SSRC of local MediaStreams and DataChannels based on the |
| // local session description. |
| const cricket::ContentInfo* audio_content = |
| GetFirstAudioContent(local_description()->description()); |
| if (audio_content) { |
| if (audio_content->rejected) { |
| RemoveSenders(cricket::MEDIA_TYPE_AUDIO); |
| } else { |
| const cricket::AudioContentDescription* audio_desc = |
| audio_content->media_description()->as_audio(); |
| UpdateLocalSenders(audio_desc->streams(), audio_desc->type()); |
| } |
| } |
| |
| const cricket::ContentInfo* video_content = |
| GetFirstVideoContent(local_description()->description()); |
| if (video_content) { |
| if (video_content->rejected) { |
| RemoveSenders(cricket::MEDIA_TYPE_VIDEO); |
| } else { |
| const cricket::VideoContentDescription* video_desc = |
| video_content->media_description()->as_video(); |
| UpdateLocalSenders(video_desc->streams(), video_desc->type()); |
| } |
| } |
| } |
| |
| const cricket::ContentInfo* data_content = |
| GetFirstDataContent(local_description()->description()); |
| if (data_content) { |
| const cricket::DataContentDescription* data_desc = |
| data_content->media_description()->as_data(); |
| if (rtc::starts_with(data_desc->protocol().data(), |
| cricket::kMediaProtocolRtpPrefix)) { |
| UpdateLocalRtpDataChannels(data_desc->streams()); |
| } |
| } |
| |
| return RTCError::OK(); |
| } |
| |
| void PeerConnection::SetRemoteDescription( |
| SetSessionDescriptionObserver* observer, |
| SessionDescriptionInterface* desc) { |
| SetRemoteDescription( |
| std::unique_ptr<SessionDescriptionInterface>(desc), |
| rtc::scoped_refptr<SetRemoteDescriptionObserverInterface>( |
| new SetRemoteDescriptionObserverAdapter(this, observer))); |
| } |
| |
| void PeerConnection::SetRemoteDescription( |
| std::unique_ptr<SessionDescriptionInterface> desc, |
| rtc::scoped_refptr<SetRemoteDescriptionObserverInterface> observer) { |
| TRACE_EVENT0("webrtc", "PeerConnection::SetRemoteDescription"); |
| |
| if (!observer) { |
| RTC_LOG(LS_ERROR) << "SetRemoteDescription - observer is NULL."; |
| return; |
| } |
| |
| if (!desc) { |
| observer->OnSetRemoteDescriptionComplete(RTCError( |
| RTCErrorType::INVALID_PARAMETER, "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) << "SetRemoteDescription: " << error_message; |
| observer->OnSetRemoteDescriptionComplete( |
| RTCError(RTCErrorType::INTERNAL_ERROR, std::move(error_message))); |
| return; |
| } |
| |
| if (desc->GetType() == SdpType::kOffer) { |
| // Report to UMA the format of the received offer. |
| ReportSdpFormatReceived(*desc); |
| } |
| |
| RTCError error = ValidateSessionDescription(desc.get(), cricket::CS_REMOTE); |
| if (!error.ok()) { |
| std::string error_message = GetSetDescriptionErrorMessage( |
| cricket::CS_REMOTE, desc->GetType(), error); |
| RTC_LOG(LS_ERROR) << error_message; |
| observer->OnSetRemoteDescriptionComplete( |
| RTCError(error.type(), std::move(error_message))); |
| return; |
| } |
| |
| // Grab the description type before moving ownership to |
| // ApplyRemoteDescription, which may destroy it before returning. |
| const SdpType type = desc->GetType(); |
| |
| error = ApplyRemoteDescription(std::move(desc)); |
| // |desc| may be destroyed at this point. |
| |
| if (!error.ok()) { |
| // If ApplyRemoteDescription fails, the PeerConnection could be in an |
| // inconsistent state, so act conservatively here and set the session error |
| // so that future calls to SetLocalDescription/SetRemoteDescription fail. |
| SetSessionError(SessionError::kContent, error.message()); |
| std::string error_message = |
| GetSetDescriptionErrorMessage(cricket::CS_REMOTE, type, error); |
| RTC_LOG(LS_ERROR) << error_message; |
| observer->OnSetRemoteDescriptionComplete( |
| RTCError(error.type(), std::move(error_message))); |
| return; |
| } |
| RTC_DCHECK(remote_description()); |
| |
| if (type == SdpType::kAnswer) { |
| // TODO(deadbeef): We already had to hop to the network thread for |
| // MaybeStartGathering... |
| network_thread()->Invoke<void>( |
| RTC_FROM_HERE, rtc::Bind(&cricket::PortAllocator::DiscardCandidatePool, |
| port_allocator_.get())); |
| // Make UMA notes about what was agreed to. |
| ReportNegotiatedSdpSemantics(*remote_description()); |
| } |
| |
| observer->OnSetRemoteDescriptionComplete(RTCError::OK()); |
| NoteUsageEvent(UsageEvent::SET_REMOTE_DESCRIPTION_CALLED); |
| } |
| |
| RTCError PeerConnection::ApplyRemoteDescription( |
| std::unique_ptr<SessionDescriptionInterface> desc) { |
| RTC_DCHECK_RUN_ON(signaling_thread()); |
| RTC_DCHECK(desc); |
| |
| // Update stats here so that we have the most recent stats for tracks and |
| // streams that might be removed by updating the session description. |
| stats_->UpdateStats(kStatsOutputLevelStandard); |
| |
| // Take a reference to the old remote description since it's used below to |
| // compare against the new remote description. When setting the new remote |
| // description, grab ownership of the replaced session description in case it |
| // is the same as |old_remote_description|, to keep it alive for the duration |
| // of the method. |
| const SessionDescriptionInterface* old_remote_description = |
| remote_description(); |
| std::unique_ptr<SessionDescriptionInterface> replaced_remote_description; |
| SdpType type = desc->GetType(); |
| if (type == SdpType::kAnswer) { |
| replaced_remote_description = pending_remote_description_ |
| ? std::move(pending_remote_description_) |
| : std::move(current_remote_description_); |
| current_remote_description_ = std::move(desc); |
| pending_remote_description_ = nullptr; |
| current_local_description_ = std::move(pending_local_description_); |
| } else { |
| replaced_remote_description = std::move(pending_remote_description_); |
| pending_remote_description_ = std::move(desc); |
| } |
| // The session description to apply now must be accessed by |
| // |remote_description()|. |
| RTC_DCHECK(remote_description()); |
| |
| RTCError error = PushdownTransportDescription(cricket::CS_REMOTE, type); |
| if (!error.ok()) { |
| return error; |
| } |
| // Transport and Media channels will be created only when offer is set. |
| if (IsUnifiedPlan()) { |
| RTCError error = UpdateTransceiversAndDataChannels( |
| cricket::CS_REMOTE, *remote_description(), local_description(), |
| old_remote_description); |
| if (!error.ok()) { |
| return error; |
| } |
| } else { |
| // Media channels will be created only when offer is set. These may use new |
| // transports just created by PushdownTransportDescription. |
| if (type == SdpType::kOffer) { |
| // TODO(mallinath) - Handle CreateChannel failure, as new local |
| // description is applied. Restore back to old description. |
| RTCError error = CreateChannels(*remote_description()->description()); |
| if (!error.ok()) { |
| return error; |
| } |
| } |
| // Remove unused channels if MediaContentDescription is rejected. |
| RemoveUnusedChannels(remote_description()->description()); |
| } |
| |
| // NOTE: Candidates allocation will be initiated only when |
| // SetLocalDescription is called. |
| error = UpdateSessionState(type, cricket::CS_REMOTE, |
| remote_description()->description()); |
| if (!error.ok()) { |
| return error; |
| } |
| |
| if (local_description() && |
| !UseCandidatesInSessionDescription(remote_description())) { |
| LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, kInvalidCandidates); |
| } |
| |
| if (old_remote_description) { |
| for (const cricket::ContentInfo& content : |
| old_remote_description->description()->contents()) { |
| // Check if this new SessionDescription contains new ICE ufrag and |
| // password that indicates the remote peer requests an ICE restart. |
| // TODO(deadbeef): When we start storing both the current and pending |
| // remote description, this should reset pending_ice_restarts and compare |
| // against the current description. |
| if (CheckForRemoteIceRestart(old_remote_description, remote_description(), |
| content.name)) { |
| if (type == SdpType::kOffer) { |
| pending_ice_restarts_.insert(content.name); |
| } |
| } else { |
| // We retain all received candidates only if ICE is not restarted. |
| // When ICE is restarted, all previous candidates belong to an old |
| // generation and should not be kept. |
| // TODO(deadbeef): This goes against the W3C spec which says the remote |
| // description should only contain candidates from the last set remote |
| // description plus any candidates added since then. We should remove |
| // this once we're sure it won't break anything. |
| WebRtcSessionDescriptionFactory::CopyCandidatesFromSessionDescription( |
| old_remote_description, content.name, mutable_remote_description()); |
| } |
| } |
| } |
| |
| if (session_error() != SessionError::kNone) { |
| LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR, GetSessionErrorMsg()); |
| } |
| |
| // Set the the ICE connection state to connecting since the connection may |
| // become writable with peer reflexive candidates before any remote candidate |
| // is signaled. |
| // TODO(pthatcher): This is a short-term solution for crbug/446908. A real fix |
| // is to have a new signal the indicates a change in checking state from the |
| // transport and expose a new checking() member from transport that can be |
| // read to determine the current checking state. The existing SignalConnecting |
| // actually means "gathering candidates", so cannot be be used here. |
| if (remote_description()->GetType() != SdpType::kOffer && |
| remote_description()->number_of_mediasections() > 0u && |
| ice_connection_state() == PeerConnectionInterface::kIceConnectionNew) { |
| SetIceConnectionState(PeerConnectionInterface::kIceConnectionChecking); |
| } |
| |
| // If setting the description decided our SSL role, allocate any necessary |
| // SCTP sids. |
| rtc::SSLRole role; |
| if (data_channel_type() == cricket::DCT_SCTP && GetSctpSslRole(&role)) { |
| AllocateSctpSids(role); |
| } |
| |
| if (IsUnifiedPlan()) { |
| std::vector<rtc::scoped_refptr<RtpTransceiverInterface>> |
| now_receiving_transceivers; |
| std::vector<rtc::scoped_refptr<RtpTransceiverInterface>> remove_list; |
| std::vector<rtc::scoped_refptr<MediaStreamInterface>> added_streams; |
| std::vector<rtc::scoped_refptr<MediaStreamInterface>> removed_streams; |
| for (auto transceiver : transceivers_) { |
| const ContentInfo* content = |
| FindMediaSectionForTransceiver(transceiver, remote_description()); |
| if (!content) { |
| continue; |
| } |
| const MediaContentDescription* media_desc = content->media_description(); |
| RtpTransceiverDirection local_direction = |
| RtpTransceiverDirectionReversed(media_desc->direction()); |
| // From the WebRTC specification, steps 2.2.8.5/6 of section 4.4.1.6 "Set |
| // the RTCSessionDescription: If direction is sendrecv or recvonly, and |
| // transceiver's current direction is neither sendrecv nor recvonly, |
| // process the addition of a remote track for the media description. |
| std::vector<std::string> stream_ids; |
| if (!media_desc->streams().empty()) { |
| // The remote description has signaled the stream IDs. |
| stream_ids = media_desc->streams()[0].stream_ids(); |
| } |
| if (RtpTransceiverDirectionHasRecv(local_direction) && |
| (!transceiver->fired_direction() || |
| !RtpTransceiverDirectionHasRecv(*transceiver->fired_direction()))) { |
| RTC_LOG(LS_INFO) << "Processing the addition of a new track for MID=" |
| << content->name << " (added to " |
| << GetStreamIdsString(stream_ids) << ")."; |
| |
| std::vector<rtc::scoped_refptr<MediaStreamInterface>> media_streams; |
| for (const std::string& stream_id : stream_ids) { |
| rtc::scoped_refptr<MediaStreamInterface> stream = |
| remote_streams_->find(stream_id); |
| if (!stream) { |
| stream = MediaStreamProxy::Create(rtc::Thread::Current(), |
| MediaStream::Create(stream_id)); |
| remote_streams_->AddStream(stream); |
| added_streams.push_back(stream); |
| } |
| media_streams.push_back(stream); |
| } |
| // This will add the remote track to the streams. |
| // TODO(hbos): When we remove remote_streams(), use set_stream_ids() |
| // instead. https://crbug.com/webrtc/9480 |
| transceiver->internal()->receiver_internal()->SetStreams(media_streams); |
| now_receiving_transceivers.push_back(transceiver); |
| } |
| // 2.2.8.1.7: If direction is "sendonly" or "inactive", and transceiver's |
| // [[FiredDirection]] slot is either "sendrecv" or "recvonly", process the |
| // removal of a remote track for the media description, given transceiver, |
| // removeList, and muteTracks. |
| if (!RtpTransceiverDirectionHasRecv(local_direction) && |
| (transceiver->fired_direction() && |
| RtpTransceiverDirectionHasRecv(*transceiver->fired_direction()))) { |
| ProcessRemovalOfRemoteTrack(transceiver, &remove_list, |
| &removed_streams); |
| } |
| // 2.2.8.1.8: Set transceiver's [[FiredDirection]] slot to direction. |
| transceiver->internal()->set_fired_direction(local_direction); |
| // 2.2.8.1.9: If description is of type "answer" or "pranswer", then run |
| // the following steps: |
| if (type == SdpType::kPrAnswer || type == SdpType::kAnswer) { |
| // 2.2.8.1.9.1: Set transceiver's [[CurrentDirection]] slot to |
| // direction. |
| transceiver->internal()->set_current_direction(local_direction); |
| } |
| // 2.2.8.1.10: If the media description is rejected, and transceiver is |
| // not already stopped, stop the RTCRtpTransceiver transceiver. |
| if (content->rejected && !transceiver->stopped()) { |
| RTC_LOG(LS_INFO) << "Stopping transceiver for MID=" << content->name |
| << " since the media section was rejected."; |
| transceiver->Stop(); |
| } |
| if (!content->rejected && |
| RtpTransceiverDirectionHasRecv(local_direction)) { |
| // Set ssrc to 0 in the case of an unsignalled ssrc. |
| uint32_t ssrc = 0; |
| if (!media_desc->streams().empty() && |
| media_desc->streams()[0].has_ssrcs()) { |
| ssrc = media_desc->streams()[0].first_ssrc(); |
| } |
| transceiver->internal()->receiver_internal()->SetupMediaChannel(ssrc); |
| } |
| } |
| // Once all processing has finished, fire off callbacks. |
| auto observer = Observer(); |
| for (auto transceiver : now_receiving_transceivers) { |
| stats_->AddTrack(transceiver->receiver()->track()); |
| observer->OnTrack(transceiver); |
| observer->OnAddTrack(transceiver->receiver(), |
| transceiver->receiver()->streams()); |
| } |
| for (auto stream : added_streams) { |
| observer->OnAddStream(stream); |
| } |
| for (auto transceiver : remove_list) { |
| observer->OnRemoveTrack(transceiver->receiver()); |
| } |
| for (auto stream : removed_streams) { |
| observer->OnRemoveStream(stream); |
| } |
| } |
| |
| const cricket::ContentInfo* audio_content = |
| GetFirstAudioContent(remote_description()->description()); |
| const cricket::ContentInfo* video_content = |
| GetFirstVideoContent(remote_description()->description()); |
| const cricket::AudioContentDescription* audio_desc = |
| GetFirstAudioContentDescription(remote_description()->description()); |
| const cricket::VideoContentDescription* video_desc = |
| GetFirstVideoContentDescription(remote_description()->description()); |
| const cricket::DataContentDescription* data_desc = |
| GetFirstDataContentDescription(remote_description()->description()); |
| |
| // Check if the descriptions include streams, just in case the peer supports |
| // MSID, but doesn't indicate so with "a=msid-semantic". |
| if (remote_description()->description()->msid_supported() || |
| (audio_desc && !audio_desc->streams().empty()) || |
| (video_desc && !video_desc->streams().empty())) { |
| remote_peer_supports_msid_ = true; |
| } |
| |
| // We wait to signal new streams until we finish processing the description, |
| // since only at that point will new streams have all their tracks. |
| rtc::scoped_refptr<StreamCollection> new_streams(StreamCollection::Create()); |
| |
| if (!IsUnifiedPlan()) { |
| // TODO(steveanton): When removing RTP senders/receivers in response to a |
| // rejected media section, there is some cleanup logic that expects the |
| // voice/ video channel to still be set. But in this method the voice/video |
| // channel would have been destroyed by the SetRemoteDescription caller |
| // above so the cleanup that relies on them fails to run. The RemoveSenders |
| // calls should be moved to right before the DestroyChannel calls to fix |
| // this. |
| |
| // Find all audio rtp streams and create corresponding remote AudioTracks |
| // and MediaStreams. |
| if (audio_content) { |
| if (audio_content->rejected) { |
| RemoveSenders(cricket::MEDIA_TYPE_AUDIO); |
| } else { |
| bool default_audio_track_needed = |
| !remote_peer_supports_msid_ && |
| RtpTransceiverDirectionHasSend(audio_desc->direction()); |
| UpdateRemoteSendersList(GetActiveStreams(audio_desc), |
| default_audio_track_needed, audio_desc->type(), |
| new_streams); |
| } |
| } |
| |
| // Find all video rtp streams and create corresponding remote VideoTracks |
| // and MediaStreams. |
| if (video_content) { |
| if (video_content->rejected) { |
| RemoveSenders(cricket::MEDIA_TYPE_VIDEO); |
| } else { |
| bool default_video_track_needed = |
| !remote_peer_supports_msid_ && |
| RtpTransceiverDirectionHasSend(video_desc->direction()); |
| UpdateRemoteSendersList(GetActiveStreams(video_desc), |
| default_video_track_needed, video_desc->type(), |
| new_streams); |
| } |
| } |
| |
| // Update the DataChannels with the information from the remote peer. |
| if (data_desc) { |
| if (rtc::starts_with(data_desc->protocol().data(), |
| cricket::kMediaProtocolRtpPrefix)) { |
| UpdateRemoteRtpDataChannels(GetActiveStreams(data_desc)); |
| } |
| } |
| |
| // Iterate new_streams and notify the observer about new MediaStreams. |
| auto observer = Observer(); |
| for (size_t i = 0; i < new_streams->count(); ++i) { |
| MediaStreamInterface* new_stream = new_streams->at(i); |
| stats_->AddStream(new_stream); |
| observer->OnAddStream( |
| rtc::scoped_refptr<MediaStreamInterface>(new_stream)); |
| } |
| |
| UpdateEndedRemoteMediaStreams(); |
| } |
| |
| return RTCError::OK(); |
| } |
| |
| void PeerConnection::ProcessRemovalOfRemoteTrack( |
| rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>> |
| transceiver, |
| std::vector<rtc::scoped_refptr<RtpTransceiverInterface>>* remove_list, |
| std::vector<rtc::scoped_refptr<MediaStreamInterface>>* removed_streams) { |
| RTC_DCHECK(transceiver->mid()); |
| RTC_LOG(LS_INFO) << "Processing the removal of a track for MID=" |
| << *transceiver->mid(); |
| std::vector<rtc::scoped_refptr<MediaStreamInterface>> media_streams = |
| transceiver->internal()->receiver_internal()->streams(); |
| // This will remove the remote track from the streams. |
| transceiver->internal()->receiver_internal()->set_stream_ids({}); |
| remove_list->push_back(transceiver); |
| // Remove any streams that no longer have tracks. |
| // TODO(https://crbug.com/webrtc/9480): When we use stream IDs instead |
| // of streams, see if the stream was removed by checking if this was the |
| // last receiver with that stream ID. |
| for (auto stream : media_streams) { |
| if (stream->GetAudioTracks().empty() && stream->GetVideoTracks().empty()) { |
| remote_streams_->RemoveStream(stream); |
| removed_streams->push_back(stream); |
| } |
| } |
| } |
| |
| RTCError PeerConnection::UpdateTransceiversAndDataChannels( |
| cricket::ContentSource source, |
| const SessionDescriptionInterface& new_session, |
| const SessionDescriptionInterface* old_local_description, |
| const SessionDescriptionInterface* old_remote_description) { |
| RTC_DCHECK(IsUnifiedPlan()); |
| |
| const cricket::ContentGroup* bundle_group = nullptr; |
| if (new_session.GetType() == SdpType::kOffer) { |
| auto bundle_group_or_error = |
| GetEarlyBundleGroup(*new_session.description()); |
| if (!bundle_group_or_error.ok()) { |
| return bundle_group_or_error.MoveError(); |
| } |
| bundle_group = bundle_group_or_error.MoveValue(); |
| } |
| |
| const ContentInfos& new_contents = new_session.description()->contents(); |
| for (size_t i = 0; i < new_contents.size(); ++i) { |
| const cricket::ContentInfo& new_content = new_contents[i]; |
| cricket::MediaType media_type = new_content.media_description()->type(); |
| seen_mids_.insert(new_content.name); |
| if (media_type == cricket::MEDIA_TYPE_AUDIO || |
| media_type == cricket::MEDIA_TYPE_VIDEO) { |
| const cricket::ContentInfo* old_local_content = nullptr; |
| if (old_local_description && |
| i < old_local_description->description()->contents().size()) { |
| old_local_content = |
| &old_local_description->description()->contents()[i]; |
| } |
| const cricket::ContentInfo* old_remote_content = nullptr; |
| if (old_remote_description && |
| i < old_remote_description->description()->contents().size()) { |
| old_remote_content = |
| &old_remote_description->description()->contents()[i]; |
| } |
| auto transceiver_or_error = |
| AssociateTransceiver(source, new_session.GetType(), i, new_content, |
| old_local_content, old_remote_content); |
| if (!transceiver_or_error.ok()) { |
| return transceiver_or_error.MoveError(); |
| } |
| auto transceiver = transceiver_or_error.MoveValue(); |
| RTCError error = |
| UpdateTransceiverChannel(transceiver, new_content, bundle_group); |
| if (!error.ok()) { |
| return error; |
| } |
| } else if (media_type == cricket::MEDIA_TYPE_DATA) { |
| if (GetDataMid() && new_content.name != *GetDataMid()) { |
| // Ignore all but the first data section. |
| RTC_LOG(LS_INFO) << "Ignoring data media section with MID=" |
| << new_content.name; |
| continue; |
| } |
| RTCError error = UpdateDataChannel(source, new_content, bundle_group); |
| if (!error.ok()) { |
| return error; |
| } |
| } else { |
| LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR, |
| "Unknown section type."); |
| } |
| } |
| |
| return RTCError::OK(); |
| } |
| |
| RTCError PeerConnection::UpdateTransceiverChannel( |
| rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>> |
| transceiver, |
| const cricket::ContentInfo& content, |
| const cricket::ContentGroup* bundle_group) { |
| RTC_DCHECK(IsUnifiedPlan()); |
| RTC_DCHECK(transceiver); |
| cricket::BaseChannel* channel = transceiver->internal()->channel(); |
| if (content.rejected) { |
| if (channel) { |
| transceiver->internal()->SetChannel(nullptr); |
| DestroyBaseChannel(channel); |
| } |
| } else { |
| if (!channel) { |
| if (transceiver->media_type() == cricket::MEDIA_TYPE_AUDIO) { |
| channel = CreateVoiceChannel(content.name); |
| } else { |
| RTC_DCHECK_EQ(cricket::MEDIA_TYPE_VIDEO, transceiver->media_type()); |
| channel = CreateVideoChannel(content.name); |
| } |
| if (!channel) { |
| LOG_AND_RETURN_ERROR( |
| RTCErrorType::INTERNAL_ERROR, |
| "Failed to create channel for mid=" + content.name); |
| } |
| transceiver->internal()->SetChannel(channel); |
| } |
| } |
| return RTCError::OK(); |
| } |
| |
| RTCError PeerConnection::UpdateDataChannel( |
| cricket::ContentSource source, |
| const cricket::ContentInfo& content, |
| const cricket::ContentGroup* bundle_group) { |
| if (data_channel_type_ == cricket::DCT_NONE) { |
| // If data channels are disabled, ignore this media section. CreateAnswer |
| // will take care of rejecting it. |
| return RTCError::OK(); |
| } |
| if (content.rejected) { |
| DestroyDataChannel(); |
| } else { |
| if (!rtp_data_channel_ && !sctp_transport_) { |
| if (!CreateDataChannel(content.name)) { |
| LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR, |
| "Failed to create data channel."); |
| } |
| } |
| if (source == cricket::CS_REMOTE) { |
| const MediaContentDescription* data_desc = content.media_description(); |
| if (data_desc && cricket::IsRtpProtocol(data_desc->protocol())) { |
| UpdateRemoteRtpDataChannels(GetActiveStreams(data_desc)); |
| } |
| } |
| } |
| return RTCError::OK(); |
| } |
| |
| RTCErrorOr<rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>> |
| PeerConnection::AssociateTransceiver(cricket::ContentSource source, |
| SdpType type, |
| size_t mline_index, |
| const ContentInfo& content, |
| const ContentInfo* old_local_content, |
| const ContentInfo* old_remote_content) { |
| RTC_DCHECK(IsUnifiedPlan()); |
| // If this is an offer then the m= section might be recycled. If the m= |
| // section is being recycled (defined as: rejected in the current local or |
| // remote description and not rejected in new description), dissociate the |
| // currently associated RtpTransceiver by setting its mid property to null, |
| // and discard the mapping between the transceiver and its m= section index. |
| if (IsMediaSectionBeingRecycled(type, content, old_local_content, |
| old_remote_content)) { |
| // We want to dissociate the transceiver that has the rejected mid. |
| const std::string& old_mid = |
| (old_local_content && old_local_content->rejected) |
| ? old_local_content->name |
| : old_remote_content->name; |
| auto old_transceiver = GetAssociatedTransceiver(old_mid); |
| if (old_transceiver) { |
| RTC_LOG(LS_INFO) << "Dissociating transceiver for MID=" << old_mid |
| << " since the media section is being recycled."; |
| old_transceiver->internal()->set_mid(absl::nullopt); |
| old_transceiver->internal()->set_mline_index(absl::nullopt); |
| } |
| } |
| const MediaContentDescription* media_desc = content.media_description(); |
| auto transceiver = GetAssociatedTransceiver(content.name); |
| if (source == cricket::CS_LOCAL) { |
| // Find the RtpTransceiver that corresponds to this m= section, using the |
| // mapping between transceivers and m= section indices established when |
| // creating the offer. |
| if (!transceiver) { |
| transceiver = GetTransceiverByMLineIndex(mline_index); |
| } |
| if (!transceiver) { |
| LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, |
| "Unknown transceiver"); |
| } |
| } else { |
| RTC_DCHECK_EQ(source, cricket::CS_REMOTE); |
| // If the m= section is sendrecv or recvonly, and there are RtpTransceivers |
| // of the same type... |
| if (!transceiver && |
| RtpTransceiverDirectionHasRecv(media_desc->direction())) { |
| transceiver = FindAvailableTransceiverToReceive(media_desc->type()); |
| } |
| // If no RtpTransceiver was found in the previous step, create one with a |
| // recvonly direction. |
| if (!transceiver) { |
| RTC_LOG(LS_INFO) << "Adding " |
| << cricket::MediaTypeToString(media_desc->type()) |
| << " transceiver for MID=" << content.name |
| << " at i=" << mline_index |
| << " in response to the remote description."; |
| std::string sender_id = rtc::CreateRandomUuid(); |
| auto sender = CreateSender(media_desc->type(), sender_id, nullptr, {}); |
| std::string receiver_id; |
| if (!media_desc->streams().empty()) { |
| receiver_id = media_desc->streams()[0].id; |
| } else { |
| receiver_id = rtc::CreateRandomUuid(); |
| } |
| auto receiver = CreateReceiver(media_desc->type(), receiver_id); |
| transceiver = CreateAndAddTransceiver(sender, receiver); |
| transceiver->internal()->set_direction( |
| RtpTransceiverDirection::kRecvOnly); |
| } |
| } |
| RTC_DCHECK(transceiver); |
| if (transceiver->media_type() != media_desc->type()) { |
| LOG_AND_RETURN_ERROR( |
| RTCErrorType::INVALID_PARAMETER, |
| "Transceiver type does not match media description type."); |
| } |
| // Associate the found or created RtpTransceiver with the m= section by |
| // setting the value of the RtpTransceiver's mid property to the MID of the m= |
| // section, and establish a mapping between the transceiver and the index of |
| // the m= section. |
| transceiver->internal()->set_mid(content.name); |
| transceiver->internal()->set_mline_index(mline_index); |
| return std::move(transceiver); |
| } |
| |
| rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>> |
| PeerConnection::GetAssociatedTransceiver(const std::string& mid) const { |
| RTC_DCHECK(IsUnifiedPlan()); |
| for (auto transceiver : transceivers_) { |
| if (transceiver->mid() == mid) { |
| return transceiver; |
| } |
| } |
| return nullptr; |
| } |
| |
| rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>> |
| PeerConnection::GetTransceiverByMLineIndex(size_t mline_index) const { |
| RTC_DCHECK(IsUnifiedPlan()); |
| for (auto transceiver : transceivers_) { |
| if (transceiver->internal()->mline_index() == mline_index) { |
| return transceiver; |
| } |
| } |
| return nullptr; |
| } |
| |
| rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>> |
| PeerConnection::FindAvailableTransceiverToReceive( |
| cricket::MediaType media_type) const { |
| RTC_DCHECK(IsUnifiedPlan()); |
| // From JSEP section 5.10 (Applying a Remote Description): |
| // If the m= section is sendrecv or recvonly, and there are RtpTransceivers of |
| // the same type that were added to the PeerConnection by addTrack and are not |
| // associated with any m= section and are not stopped, find the first such |
| // RtpTransceiver. |
| for (auto transceiver : transceivers_) { |
| if (transceiver->media_type() == media_type && |
| transceiver->internal()->created_by_addtrack() && !transceiver->mid() && |
| !transceiver->stopped()) { |
| return transceiver; |
| } |
| } |
| return nullptr; |
| } |
| |
| const cricket::ContentInfo* PeerConnection::FindMediaSectionForTransceiver( |
| rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>> |
| transceiver, |
| const SessionDescriptionInterface* sdesc) const { |
| RTC_DCHECK(transceiver); |
| RTC_DCHECK(sdesc); |
| if (IsUnifiedPlan()) { |
| if (!transceiver->internal()->mid()) { |
| // This transceiver is not associated with a media section yet. |
| return nullptr; |
| } |
| return sdesc->description()->GetContentByName( |
| *transceiver->internal()->mid()); |
| } else { |
| // Plan B only allows at most one audio and one video section, so use the |
| // first media section of that type. |
| return cricket::GetFirstMediaContent(sdesc->description()->contents(), |
| transceiver->media_type()); |
| } |
| } |
| |
| PeerConnectionInterface::RTCConfiguration PeerConnection::GetConfiguration() { |
| return configuration_; |
| } |
| |
| bool PeerConnection::SetConfiguration(const RTCConfiguration& configuration, |
| RTCError* error) { |
| TRACE_EVENT0("webrtc", "PeerConnection::SetConfiguration"); |
| if (IsClosed()) { |
| RTC_LOG(LS_ERROR) << "SetConfiguration: PeerConnection is closed."; |
| return SafeSetError(RTCErrorType::INVALID_STATE, error); |
| } |
| |
| // According to JSEP, after setLocalDescription, changing the candidate pool |
| // size is not allowed, and changing the set of ICE servers will not result |
| // in new candidates being gathered. |
| if (local_description() |