/* * 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 "webrtc/api/peerconnection.h" #include #include // for isdigit #include #include #include "webrtc/api/audiotrack.h" #include "webrtc/api/dtmfsender.h" #include "webrtc/api/jsepicecandidate.h" #include "webrtc/api/jsepsessiondescription.h" #include "webrtc/api/mediaconstraintsinterface.h" #include "webrtc/api/mediastream.h" #include "webrtc/api/mediastreamobserver.h" #include "webrtc/api/mediastreamproxy.h" #include "webrtc/api/mediastreamtrackproxy.h" #include "webrtc/api/remoteaudiosource.h" #include "webrtc/api/rtpreceiver.h" #include "webrtc/api/rtpsender.h" #include "webrtc/api/streamcollection.h" #include "webrtc/api/videocapturertracksource.h" #include "webrtc/api/videotrack.h" #include "webrtc/base/arraysize.h" #include "webrtc/base/logging.h" #include "webrtc/base/stringencode.h" #include "webrtc/base/stringutils.h" #include "webrtc/base/trace_event.h" #include "webrtc/media/sctp/sctpdataengine.h" #include "webrtc/pc/channelmanager.h" #include "webrtc/system_wrappers/include/field_trial.h" namespace { using webrtc::DataChannel; using webrtc::MediaConstraintsInterface; using webrtc::MediaStreamInterface; using webrtc::PeerConnectionInterface; using webrtc::RtpSenderInternal; using webrtc::RtpSenderInterface; using webrtc::RtpSenderProxy; using webrtc::RtpSenderProxyWithInternal; using webrtc::StreamCollection; static const char kDefaultStreamLabel[] = "default"; static const char kDefaultAudioTrackLabel[] = "defaulta0"; static const char kDefaultVideoTrackLabel[] = "defaultv0"; // The min number of tokens must present in Turn host uri. // e.g. user@turn.example.org static const size_t kTurnHostTokensNum = 2; // Number of tokens must be preset when TURN uri has transport param. static const size_t kTurnTransportTokensNum = 2; // The default stun port. static const int kDefaultStunPort = 3478; static const int kDefaultStunTlsPort = 5349; static const char kTransport[] = "transport"; // NOTE: Must be in the same order as the ServiceType enum. static const char* kValidIceServiceTypes[] = {"stun", "stuns", "turn", "turns"}; // The length of RTCP CNAMEs. static const int kRtcpCnameLength = 16; // NOTE: A loop below assumes that the first value of this enum is 0 and all // other values are incremental. enum ServiceType { STUN = 0, // Indicates a STUN server. STUNS, // Indicates a STUN server used with a TLS session. TURN, // Indicates a TURN server TURNS, // Indicates a TURN server used with a TLS session. INVALID, // Unknown. }; static_assert(INVALID == arraysize(kValidIceServiceTypes), "kValidIceServiceTypes must have as many strings as ServiceType " "has values."); enum { MSG_SET_SESSIONDESCRIPTION_SUCCESS = 0, MSG_SET_SESSIONDESCRIPTION_FAILED, MSG_CREATE_SESSIONDESCRIPTION_FAILED, MSG_GETSTATS, MSG_FREE_DATACHANNELS, }; struct SetSessionDescriptionMsg : public rtc::MessageData { explicit SetSessionDescriptionMsg( webrtc::SetSessionDescriptionObserver* observer) : observer(observer) { } rtc::scoped_refptr observer; std::string error; }; struct CreateSessionDescriptionMsg : public rtc::MessageData { explicit CreateSessionDescriptionMsg( webrtc::CreateSessionDescriptionObserver* observer) : observer(observer) {} rtc::scoped_refptr observer; std::string error; }; struct GetStatsMsg : public rtc::MessageData { GetStatsMsg(webrtc::StatsObserver* observer, webrtc::MediaStreamTrackInterface* track) : observer(observer), track(track) { } rtc::scoped_refptr observer; rtc::scoped_refptr track; }; // |in_str| should be of format // stunURI = scheme ":" stun-host [ ":" stun-port ] // scheme = "stun" / "stuns" // stun-host = IP-literal / IPv4address / reg-name // stun-port = *DIGIT // // draft-petithuguenin-behave-turn-uris-01 // turnURI = scheme ":" turn-host [ ":" turn-port ] // turn-host = username@IP-literal / IPv4address / reg-name bool GetServiceTypeAndHostnameFromUri(const std::string& in_str, ServiceType* service_type, std::string* hostname) { const std::string::size_type colonpos = in_str.find(':'); if (colonpos == std::string::npos) { LOG(LS_WARNING) << "Missing ':' in ICE URI: " << in_str; return false; } if ((colonpos + 1) == in_str.length()) { LOG(LS_WARNING) << "Empty hostname in ICE URI: " << in_str; return false; } *service_type = INVALID; for (size_t i = 0; i < arraysize(kValidIceServiceTypes); ++i) { if (in_str.compare(0, colonpos, kValidIceServiceTypes[i]) == 0) { *service_type = static_cast(i); break; } } if (*service_type == INVALID) { return false; } *hostname = in_str.substr(colonpos + 1, std::string::npos); return true; } bool ParsePort(const std::string& in_str, int* port) { // Make sure port only contains digits. FromString doesn't check this. for (const char& c : in_str) { if (!std::isdigit(c)) { return false; } } return rtc::FromString(in_str, port); } // This method parses IPv6 and IPv4 literal strings, along with hostnames in // standard hostname:port format. // Consider following formats as correct. // |hostname:port|, |[IPV6 address]:port|, |IPv4 address|:port, // |hostname|, |[IPv6 address]|, |IPv4 address|. bool ParseHostnameAndPortFromString(const std::string& in_str, std::string* host, int* port) { RTC_DCHECK(host->empty()); if (in_str.at(0) == '[') { std::string::size_type closebracket = in_str.rfind(']'); if (closebracket != std::string::npos) { std::string::size_type colonpos = in_str.find(':', closebracket); if (std::string::npos != colonpos) { if (!ParsePort(in_str.substr(closebracket + 2, std::string::npos), port)) { return false; } } *host = in_str.substr(1, closebracket - 1); } else { return false; } } else { std::string::size_type colonpos = in_str.find(':'); if (std::string::npos != colonpos) { if (!ParsePort(in_str.substr(colonpos + 1, std::string::npos), port)) { return false; } *host = in_str.substr(0, colonpos); } else { *host = in_str; } } return !host->empty(); } // Adds a STUN or TURN server to the appropriate list, // by parsing |url| and using the username/password in |server|. bool ParseIceServerUrl(const PeerConnectionInterface::IceServer& server, const std::string& url, cricket::ServerAddresses* stun_servers, std::vector* turn_servers) { // draft-nandakumar-rtcweb-stun-uri-01 // stunURI = scheme ":" stun-host [ ":" stun-port ] // scheme = "stun" / "stuns" // stun-host = IP-literal / IPv4address / reg-name // stun-port = *DIGIT // draft-petithuguenin-behave-turn-uris-01 // turnURI = scheme ":" turn-host [ ":" turn-port ] // [ "?transport=" transport ] // scheme = "turn" / "turns" // transport = "udp" / "tcp" / transport-ext // transport-ext = 1*unreserved // turn-host = IP-literal / IPv4address / reg-name // turn-port = *DIGIT RTC_DCHECK(stun_servers != nullptr); RTC_DCHECK(turn_servers != nullptr); std::vector tokens; cricket::ProtocolType turn_transport_type = cricket::PROTO_UDP; RTC_DCHECK(!url.empty()); rtc::tokenize(url, '?', &tokens); std::string uri_without_transport = tokens[0]; // Let's look into transport= param, if it exists. if (tokens.size() == kTurnTransportTokensNum) { // ?transport= is present. std::string uri_transport_param = tokens[1]; rtc::tokenize(uri_transport_param, '=', &tokens); if (tokens[0] == kTransport) { // As per above grammar transport param will be consist of lower case // letters. if (!cricket::StringToProto(tokens[1].c_str(), &turn_transport_type) || (turn_transport_type != cricket::PROTO_UDP && turn_transport_type != cricket::PROTO_TCP)) { LOG(LS_WARNING) << "Transport param should always be udp or tcp."; return false; } } } std::string hoststring; ServiceType service_type; if (!GetServiceTypeAndHostnameFromUri(uri_without_transport, &service_type, &hoststring)) { LOG(LS_WARNING) << "Invalid transport parameter in ICE URI: " << url; return false; } // GetServiceTypeAndHostnameFromUri should never give an empty hoststring RTC_DCHECK(!hoststring.empty()); // Let's break hostname. tokens.clear(); rtc::tokenize_with_empty_tokens(hoststring, '@', &tokens); std::string username(server.username); if (tokens.size() > kTurnHostTokensNum) { LOG(LS_WARNING) << "Invalid user@hostname format: " << hoststring; return false; } if (tokens.size() == kTurnHostTokensNum) { if (tokens[0].empty() || tokens[1].empty()) { LOG(LS_WARNING) << "Invalid user@hostname format: " << hoststring; return false; } username.assign(rtc::s_url_decode(tokens[0])); hoststring = tokens[1]; } else { hoststring = tokens[0]; } int port = kDefaultStunPort; if (service_type == TURNS) { port = kDefaultStunTlsPort; turn_transport_type = cricket::PROTO_TCP; } std::string address; if (!ParseHostnameAndPortFromString(hoststring, &address, &port)) { LOG(WARNING) << "Invalid hostname format: " << uri_without_transport; return false; } if (port <= 0 || port > 0xffff) { LOG(WARNING) << "Invalid port: " << port; return false; } switch (service_type) { case STUN: case STUNS: stun_servers->insert(rtc::SocketAddress(address, port)); break; case TURN: case TURNS: { bool secure = (service_type == TURNS); turn_servers->push_back( cricket::RelayServerConfig(address, port, username, server.password, turn_transport_type, secure)); break; } case INVALID: default: LOG(WARNING) << "Configuration not supported: " << url; return false; } return true; } // 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->label()) != nullptr) { LOG(LS_ERROR) << "MediaStream with label " << new_stream->label() << " is already added."; return false; } return true; } bool MediaContentDirectionHasSend(cricket::MediaContentDirection dir) { return dir == cricket::MD_SENDONLY || dir == cricket::MD_SENDRECV; } // 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 GetActiveStreams( const cricket::MediaContentDescription* desc) { return MediaContentDirectionHasSend(desc->direction()) ? desc->streams() : std::vector(); } bool IsValidOfferToReceiveMedia(int value) { typedef PeerConnectionInterface::RTCOfferAnswerOptions Options; return (value >= Options::kUndefined) && (value <= Options::kMaxOfferToReceiveMedia); } // Add the stream and RTP data channel info to |session_options|. void AddSendStreams( cricket::MediaSessionOptions* session_options, const std::vector>>& senders, const std::map>& rtp_data_channels) { session_options->streams.clear(); for (const auto& sender : senders) { session_options->AddSendStream(sender->media_type(), sender->id(), sender->internal()->stream_id()); } // 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) { // |streamid| and |sync_label| are both set to the DataChannel label // here so they can be signaled the same way as MediaStreams and Tracks. // For MediaStreams, the sync_label is the MediaStream label and the // track label is the same as |streamid|. const std::string& streamid = channel->label(); const std::string& sync_label = channel->label(); session_options->AddSendStream(cricket::MEDIA_TYPE_DATA, streamid, sync_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: ASSERT(false); } return cricket::CF_NONE; } } // namespace namespace webrtc { // Generate a RTCP CNAME when a PeerConnection is created. std::string GenerateRtcpCname() { std::string cname; if (!rtc::CreateRandomString(kRtcpCnameLength, &cname)) { LOG(LS_ERROR) << "Failed to generate CNAME."; RTC_DCHECK(false); } return cname; } bool ExtractMediaSessionOptions( const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options, bool is_offer, cricket::MediaSessionOptions* session_options) { typedef PeerConnectionInterface::RTCOfferAnswerOptions RTCOfferAnswerOptions; if (!IsValidOfferToReceiveMedia(rtc_options.offer_to_receive_audio) || !IsValidOfferToReceiveMedia(rtc_options.offer_to_receive_video)) { return false; } // If constraints don't prevent us, we always accept video. if (rtc_options.offer_to_receive_audio != RTCOfferAnswerOptions::kUndefined) { session_options->recv_audio = (rtc_options.offer_to_receive_audio > 0); } else { session_options->recv_audio = true; } // For offers, we only offer video if we have it or it's forced by options. // For answers, we will always accept video (if offered). if (rtc_options.offer_to_receive_video != RTCOfferAnswerOptions::kUndefined) { session_options->recv_video = (rtc_options.offer_to_receive_video > 0); } else if (is_offer) { session_options->recv_video = false; } else { session_options->recv_video = true; } session_options->vad_enabled = rtc_options.voice_activity_detection; session_options->bundle_enabled = rtc_options.use_rtp_mux; for (auto& kv : session_options->transport_options) { kv.second.ice_restart = rtc_options.ice_restart; } return true; } bool ParseConstraintsForAnswer(const MediaConstraintsInterface* constraints, cricket::MediaSessionOptions* session_options) { bool value = false; size_t mandatory_constraints_satisfied = 0; // kOfferToReceiveAudio defaults to true according to spec. if (!FindConstraint(constraints, MediaConstraintsInterface::kOfferToReceiveAudio, &value, &mandatory_constraints_satisfied) || value) { session_options->recv_audio = true; } // kOfferToReceiveVideo defaults to false according to spec. But // if it is an answer and video is offered, we should still accept video // per default. value = false; if (!FindConstraint(constraints, MediaConstraintsInterface::kOfferToReceiveVideo, &value, &mandatory_constraints_satisfied) || value) { session_options->recv_video = true; } if (FindConstraint(constraints, MediaConstraintsInterface::kVoiceActivityDetection, &value, &mandatory_constraints_satisfied)) { session_options->vad_enabled = value; } if (FindConstraint(constraints, MediaConstraintsInterface::kUseRtpMux, &value, &mandatory_constraints_satisfied)) { session_options->bundle_enabled = value; } else { // kUseRtpMux defaults to true according to spec. session_options->bundle_enabled = true; } bool ice_restart = false; if (FindConstraint(constraints, MediaConstraintsInterface::kIceRestart, &value, &mandatory_constraints_satisfied)) { // kIceRestart defaults to false according to spec. ice_restart = true; } for (auto& kv : session_options->transport_options) { kv.second.ice_restart = ice_restart; } if (!constraints) { return true; } return mandatory_constraints_satisfied == constraints->GetMandatory().size(); } bool ParseIceServers(const PeerConnectionInterface::IceServers& servers, cricket::ServerAddresses* stun_servers, std::vector* turn_servers) { for (const webrtc::PeerConnectionInterface::IceServer& server : servers) { if (!server.urls.empty()) { for (const std::string& url : server.urls) { if (url.empty()) { LOG(LS_ERROR) << "Empty uri."; return false; } if (!ParseIceServerUrl(server, url, stun_servers, turn_servers)) { return false; } } } else if (!server.uri.empty()) { // Fallback to old .uri if new .urls isn't present. if (!ParseIceServerUrl(server, server.uri, stun_servers, turn_servers)) { return false; } } else { LOG(LS_ERROR) << "Empty uri."; return false; } } // Candidates must have unique priorities, so that connectivity checks // are performed in a well-defined order. int priority = static_cast(turn_servers->size() - 1); for (cricket::RelayServerConfig& turn_server : *turn_servers) { // First in the list gets highest priority. turn_server.priority = priority--; } return true; } PeerConnection::PeerConnection(PeerConnectionFactory* factory) : factory_(factory), observer_(NULL), uma_observer_(NULL), signaling_state_(kStable), ice_state_(kIceNew), ice_connection_state_(kIceConnectionNew), ice_gathering_state_(kIceGatheringNew), rtcp_cname_(GenerateRtcpCname()), local_streams_(StreamCollection::Create()), remote_streams_(StreamCollection::Create()) {} PeerConnection::~PeerConnection() { TRACE_EVENT0("webrtc", "PeerConnection::~PeerConnection"); RTC_DCHECK(signaling_thread()->IsCurrent()); // Need to detach RTP senders/receivers from WebRtcSession, // since it's about to be destroyed. for (const auto& sender : senders_) { sender->internal()->Stop(); } for (const auto& receiver : receivers_) { receiver->internal()->Stop(); } // Destroy stats_ because it depends on session_. stats_.reset(nullptr); // Now destroy session_ before destroying other members, // because its destruction fires signals (such as VoiceChannelDestroyed) // which will trigger some final actions in PeerConnection... session_.reset(nullptr); // port_allocator_ lives on the network thread and should be destroyed there. network_thread()->Invoke(RTC_FROM_HERE, [this] { port_allocator_.reset(nullptr); }); } bool PeerConnection::Initialize( const PeerConnectionInterface::RTCConfiguration& configuration, std::unique_ptr allocator, std::unique_ptr cert_generator, PeerConnectionObserver* observer) { TRACE_EVENT0("webrtc", "PeerConnection::Initialize"); RTC_DCHECK(observer != nullptr); if (!observer) { return false; } observer_ = observer; port_allocator_ = std::move(allocator); // The port allocator lives on the network thread and should be initialized // there. if (!network_thread()->Invoke( RTC_FROM_HERE, rtc::Bind(&PeerConnection::InitializePortAllocator_n, this, configuration))) { return false; } media_controller_.reset( factory_->CreateMediaController(configuration.media_config)); session_.reset( new WebRtcSession(media_controller_.get(), factory_->network_thread(), factory_->worker_thread(), factory_->signaling_thread(), port_allocator_.get())); stats_.reset(new StatsCollector(this)); // Initialize the WebRtcSession. It creates transport channels etc. if (!session_->Initialize(factory_->options(), std::move(cert_generator), configuration)) { return false; } // Register PeerConnection as receiver of local ice candidates. // All the callbacks will be posted to the application from PeerConnection. session_->RegisterIceObserver(this); session_->SignalState.connect(this, &PeerConnection::OnSessionStateChange); session_->SignalVoiceChannelDestroyed.connect( this, &PeerConnection::OnVoiceChannelDestroyed); session_->SignalVideoChannelDestroyed.connect( this, &PeerConnection::OnVideoChannelDestroyed); session_->SignalDataChannelCreated.connect( this, &PeerConnection::OnDataChannelCreated); session_->SignalDataChannelDestroyed.connect( this, &PeerConnection::OnDataChannelDestroyed); session_->SignalDataChannelOpenMessage.connect( this, &PeerConnection::OnDataChannelOpenMessage); return true; } rtc::scoped_refptr PeerConnection::local_streams() { return local_streams_; } rtc::scoped_refptr PeerConnection::remote_streams() { return remote_streams_; } bool PeerConnection::AddStream(MediaStreamInterface* local_stream) { 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(observer)); for (const auto& track : local_stream->GetAudioTracks()) { OnAudioTrackAdded(track.get(), local_stream); } for (const auto& track : local_stream->GetVideoTracks()) { OnVideoTrackAdded(track.get(), local_stream); } stats_->AddStream(local_stream); observer_->OnRenegotiationNeeded(); return true; } void PeerConnection::RemoveStream(MediaStreamInterface* local_stream) { TRACE_EVENT0("webrtc", "PeerConnection::RemoveStream"); for (const auto& track : local_stream->GetAudioTracks()) { OnAudioTrackRemoved(track.get(), local_stream); } for (const auto& track : local_stream->GetVideoTracks()) { OnVideoTrackRemoved(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& observer) { return observer->stream()->label().compare(local_stream->label()) == 0; }), stream_observers_.end()); if (IsClosed()) { return; } observer_->OnRenegotiationNeeded(); } rtc::scoped_refptr PeerConnection::AddTrack( MediaStreamTrackInterface* track, std::vector streams) { TRACE_EVENT0("webrtc", "PeerConnection::AddTrack"); if (IsClosed()) { return nullptr; } if (streams.size() >= 2) { LOG(LS_ERROR) << "Adding a track with two streams is not currently supported."; return nullptr; } // TODO(deadbeef): Support adding a track to two different senders. if (FindSenderForTrack(track) != senders_.end()) { LOG(LS_ERROR) << "Sender for track " << track->id() << " already exists."; return nullptr; } // TODO(deadbeef): Support adding a track to multiple streams. rtc::scoped_refptr> new_sender; if (track->kind() == MediaStreamTrackInterface::kAudioKind) { new_sender = RtpSenderProxyWithInternal::Create( signaling_thread(), new AudioRtpSender(static_cast(track), session_.get(), stats_.get())); if (!streams.empty()) { new_sender->internal()->set_stream_id(streams[0]->label()); } const TrackInfo* track_info = FindTrackInfo( local_audio_tracks_, new_sender->internal()->stream_id(), track->id()); if (track_info) { new_sender->internal()->SetSsrc(track_info->ssrc); } } else if (track->kind() == MediaStreamTrackInterface::kVideoKind) { new_sender = RtpSenderProxyWithInternal::Create( signaling_thread(), new VideoRtpSender(static_cast(track), session_.get())); if (!streams.empty()) { new_sender->internal()->set_stream_id(streams[0]->label()); } const TrackInfo* track_info = FindTrackInfo( local_video_tracks_, new_sender->internal()->stream_id(), track->id()); if (track_info) { new_sender->internal()->SetSsrc(track_info->ssrc); } } else { LOG(LS_ERROR) << "CreateSender called with invalid kind: " << track->kind(); return rtc::scoped_refptr(); } senders_.push_back(new_sender); observer_->OnRenegotiationNeeded(); return new_sender; } bool PeerConnection::RemoveTrack(RtpSenderInterface* sender) { TRACE_EVENT0("webrtc", "PeerConnection::RemoveTrack"); if (IsClosed()) { return false; } auto it = std::find(senders_.begin(), senders_.end(), sender); if (it == senders_.end()) { LOG(LS_ERROR) << "Couldn't find sender " << sender->id() << " to remove."; return false; } (*it)->internal()->Stop(); senders_.erase(it); observer_->OnRenegotiationNeeded(); return true; } rtc::scoped_refptr PeerConnection::CreateDtmfSender( AudioTrackInterface* track) { TRACE_EVENT0("webrtc", "PeerConnection::CreateDtmfSender"); if (!track) { LOG(LS_ERROR) << "CreateDtmfSender - track is NULL."; return NULL; } if (!local_streams_->FindAudioTrack(track->id())) { LOG(LS_ERROR) << "CreateDtmfSender is called with a non local audio track."; return NULL; } rtc::scoped_refptr sender( DtmfSender::Create(track, signaling_thread(), session_.get())); if (!sender.get()) { LOG(LS_ERROR) << "CreateDtmfSender failed on DtmfSender::Create."; return NULL; } return DtmfSenderProxy::Create(signaling_thread(), sender.get()); } rtc::scoped_refptr PeerConnection::CreateSender( const std::string& kind, const std::string& stream_id) { TRACE_EVENT0("webrtc", "PeerConnection::CreateSender"); rtc::scoped_refptr> new_sender; if (kind == MediaStreamTrackInterface::kAudioKind) { new_sender = RtpSenderProxyWithInternal::Create( signaling_thread(), new AudioRtpSender(session_.get(), stats_.get())); } else if (kind == MediaStreamTrackInterface::kVideoKind) { new_sender = RtpSenderProxyWithInternal::Create( signaling_thread(), new VideoRtpSender(session_.get())); } else { LOG(LS_ERROR) << "CreateSender called with invalid kind: " << kind; return new_sender; } if (!stream_id.empty()) { new_sender->internal()->set_stream_id(stream_id); } senders_.push_back(new_sender); return new_sender; } std::vector> PeerConnection::GetSenders() const { std::vector> ret; for (const auto& sender : senders_) { ret.push_back(sender.get()); } return ret; } std::vector> PeerConnection::GetReceivers() const { std::vector> ret; for (const auto& receiver : receivers_) { ret.push_back(receiver.get()); } return ret; } bool PeerConnection::GetStats(StatsObserver* observer, MediaStreamTrackInterface* track, StatsOutputLevel level) { TRACE_EVENT0("webrtc", "PeerConnection::GetStats"); RTC_DCHECK(signaling_thread()->IsCurrent()); if (!VERIFY(observer != NULL)) { LOG(LS_ERROR) << "GetStats - observer is NULL."; return false; } stats_->UpdateStats(level); signaling_thread()->Post(RTC_FROM_HERE, this, MSG_GETSTATS, new GetStatsMsg(observer, track)); return true; } PeerConnectionInterface::SignalingState PeerConnection::signaling_state() { return signaling_state_; } PeerConnectionInterface::IceState PeerConnection::ice_state() { return ice_state_; } PeerConnectionInterface::IceConnectionState PeerConnection::ice_connection_state() { return ice_connection_state_; } PeerConnectionInterface::IceGatheringState PeerConnection::ice_gathering_state() { return ice_gathering_state_; } rtc::scoped_refptr PeerConnection::CreateDataChannel( const std::string& label, const DataChannelInit* config) { TRACE_EVENT0("webrtc", "PeerConnection::CreateDataChannel"); bool first_datachannel = !HasDataChannels(); std::unique_ptr internal_config; if (config) { internal_config.reset(new InternalDataChannelInit(*config)); } rtc::scoped_refptr 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 (session_->data_channel_type() == cricket::DCT_RTP || first_datachannel) { observer_->OnRenegotiationNeeded(); } return DataChannelProxy::Create(signaling_thread(), channel.get()); } void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer, const MediaConstraintsInterface* constraints) { TRACE_EVENT0("webrtc", "PeerConnection::CreateOffer"); if (!VERIFY(observer != nullptr)) { LOG(LS_ERROR) << "CreateOffer - observer is NULL."; return; } RTCOfferAnswerOptions options; bool value; size_t mandatory_constraints = 0; if (FindConstraint(constraints, MediaConstraintsInterface::kOfferToReceiveAudio, &value, &mandatory_constraints)) { options.offer_to_receive_audio = value ? RTCOfferAnswerOptions::kOfferToReceiveMediaTrue : 0; } if (FindConstraint(constraints, MediaConstraintsInterface::kOfferToReceiveVideo, &value, &mandatory_constraints)) { options.offer_to_receive_video = value ? RTCOfferAnswerOptions::kOfferToReceiveMediaTrue : 0; } if (FindConstraint(constraints, MediaConstraintsInterface::kVoiceActivityDetection, &value, &mandatory_constraints)) { options.voice_activity_detection = value; } if (FindConstraint(constraints, MediaConstraintsInterface::kIceRestart, &value, &mandatory_constraints)) { options.ice_restart = value; } if (FindConstraint(constraints, MediaConstraintsInterface::kUseRtpMux, &value, &mandatory_constraints)) { options.use_rtp_mux = value; } CreateOffer(observer, options); } void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer, const RTCOfferAnswerOptions& options) { TRACE_EVENT0("webrtc", "PeerConnection::CreateOffer"); if (!VERIFY(observer != nullptr)) { LOG(LS_ERROR) << "CreateOffer - observer is NULL."; return; } cricket::MediaSessionOptions session_options; if (!GetOptionsForOffer(options, &session_options)) { std::string error = "CreateOffer called with invalid options."; LOG(LS_ERROR) << error; PostCreateSessionDescriptionFailure(observer, error); return; } session_->CreateOffer(observer, options, session_options); } void PeerConnection::CreateAnswer( CreateSessionDescriptionObserver* observer, const MediaConstraintsInterface* constraints) { TRACE_EVENT0("webrtc", "PeerConnection::CreateAnswer"); if (!VERIFY(observer != nullptr)) { LOG(LS_ERROR) << "CreateAnswer - observer is NULL."; return; } cricket::MediaSessionOptions session_options; if (!GetOptionsForAnswer(constraints, &session_options)) { std::string error = "CreateAnswer called with invalid constraints."; LOG(LS_ERROR) << error; PostCreateSessionDescriptionFailure(observer, error); return; } session_->CreateAnswer(observer, session_options); } void PeerConnection::CreateAnswer(CreateSessionDescriptionObserver* observer, const RTCOfferAnswerOptions& options) { TRACE_EVENT0("webrtc", "PeerConnection::CreateAnswer"); if (!VERIFY(observer != nullptr)) { LOG(LS_ERROR) << "CreateAnswer - observer is NULL."; return; } cricket::MediaSessionOptions session_options; if (!GetOptionsForAnswer(options, &session_options)) { std::string error = "CreateAnswer called with invalid options."; LOG(LS_ERROR) << error; PostCreateSessionDescriptionFailure(observer, error); return; } session_->CreateAnswer(observer, session_options); } void PeerConnection::SetLocalDescription( SetSessionDescriptionObserver* observer, SessionDescriptionInterface* desc) { TRACE_EVENT0("webrtc", "PeerConnection::SetLocalDescription"); if (!VERIFY(observer != nullptr)) { LOG(LS_ERROR) << "SetLocalDescription - observer is NULL."; return; } if (!desc) { PostSetSessionDescriptionFailure(observer, "SessionDescription is NULL."); return; } // 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); std::string error; if (!session_->SetLocalDescription(desc, &error)) { PostSetSessionDescriptionFailure(observer, error); return; } // If setting the description decided our SSL role, allocate any necessary // SCTP sids. rtc::SSLRole role; if (session_->data_channel_type() == cricket::DCT_SCTP && session_->GetSslRole(session_->data_channel(), &role)) { AllocateSctpSids(role); } // Update state and SSRC of local MediaStreams and DataChannels based on the // local session description. const cricket::ContentInfo* audio_content = GetFirstAudioContent(desc->description()); if (audio_content) { if (audio_content->rejected) { RemoveTracks(cricket::MEDIA_TYPE_AUDIO); } else { const cricket::AudioContentDescription* audio_desc = static_cast( audio_content->description); UpdateLocalTracks(audio_desc->streams(), audio_desc->type()); } } const cricket::ContentInfo* video_content = GetFirstVideoContent(desc->description()); if (video_content) { if (video_content->rejected) { RemoveTracks(cricket::MEDIA_TYPE_VIDEO); } else { const cricket::VideoContentDescription* video_desc = static_cast( video_content->description); UpdateLocalTracks(video_desc->streams(), video_desc->type()); } } const cricket::ContentInfo* data_content = GetFirstDataContent(desc->description()); if (data_content) { const cricket::DataContentDescription* data_desc = static_cast( data_content->description); if (rtc::starts_with(data_desc->protocol().data(), cricket::kMediaProtocolRtpPrefix)) { UpdateLocalRtpDataChannels(data_desc->streams()); } } SetSessionDescriptionMsg* msg = new SetSessionDescriptionMsg(observer); signaling_thread()->Post(RTC_FROM_HERE, this, MSG_SET_SESSIONDESCRIPTION_SUCCESS, msg); // MaybeStartGathering needs to be called after posting // MSG_SET_SESSIONDESCRIPTION_SUCCESS, so that we don't signal any candidates // before signaling that SetLocalDescription completed. session_->MaybeStartGathering(); } void PeerConnection::SetRemoteDescription( SetSessionDescriptionObserver* observer, SessionDescriptionInterface* desc) { TRACE_EVENT0("webrtc", "PeerConnection::SetRemoteDescription"); if (!VERIFY(observer != nullptr)) { LOG(LS_ERROR) << "SetRemoteDescription - observer is NULL."; return; } if (!desc) { PostSetSessionDescriptionFailure(observer, "SessionDescription is NULL."); return; } // 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); std::string error; if (!session_->SetRemoteDescription(desc, &error)) { PostSetSessionDescriptionFailure(observer, error); return; } // If setting the description decided our SSL role, allocate any necessary // SCTP sids. rtc::SSLRole role; if (session_->data_channel_type() == cricket::DCT_SCTP && session_->GetSslRole(session_->data_channel(), &role)) { AllocateSctpSids(role); } const cricket::SessionDescription* remote_desc = desc->description(); const cricket::ContentInfo* audio_content = GetFirstAudioContent(remote_desc); const cricket::ContentInfo* video_content = GetFirstVideoContent(remote_desc); const cricket::AudioContentDescription* audio_desc = GetFirstAudioContentDescription(remote_desc); const cricket::VideoContentDescription* video_desc = GetFirstVideoContentDescription(remote_desc); const cricket::DataContentDescription* data_desc = GetFirstDataContentDescription(remote_desc); // Check if the descriptions include streams, just in case the peer supports // MSID, but doesn't indicate so with "a=msid-semantic". if (remote_desc->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 new_streams(StreamCollection::Create()); // Find all audio rtp streams and create corresponding remote AudioTracks // and MediaStreams. if (audio_content) { if (audio_content->rejected) { RemoveTracks(cricket::MEDIA_TYPE_AUDIO); } else { bool default_audio_track_needed = !remote_peer_supports_msid_ && MediaContentDirectionHasSend(audio_desc->direction()); UpdateRemoteStreamsList(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) { RemoveTracks(cricket::MEDIA_TYPE_VIDEO); } else { bool default_video_track_needed = !remote_peer_supports_msid_ && MediaContentDirectionHasSend(video_desc->direction()); UpdateRemoteStreamsList(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. for (size_t i = 0; i < new_streams->count(); ++i) { MediaStreamInterface* new_stream = new_streams->at(i); stats_->AddStream(new_stream); // Call both the raw pointer and scoped_refptr versions of the method // for compatibility. observer_->OnAddStream(new_stream); observer_->OnAddStream( rtc::scoped_refptr(new_stream)); } UpdateEndedRemoteMediaStreams(); SetSessionDescriptionMsg* msg = new SetSessionDescriptionMsg(observer); signaling_thread()->Post(RTC_FROM_HERE, this, MSG_SET_SESSIONDESCRIPTION_SUCCESS, msg); } bool PeerConnection::SetConfiguration(const RTCConfiguration& configuration) { TRACE_EVENT0("webrtc", "PeerConnection::SetConfiguration"); if (port_allocator_) { if (!network_thread()->Invoke( RTC_FROM_HERE, rtc::Bind(&PeerConnection::ReconfigurePortAllocator_n, this, configuration))) { return false; } } // TODO(deadbeef): Shouldn't have to hop to the worker thread twice... session_->SetIceConfig(session_->ParseIceConfig(configuration)); return true; } bool PeerConnection::AddIceCandidate( const IceCandidateInterface* ice_candidate) { TRACE_EVENT0("webrtc", "PeerConnection::AddIceCandidate"); return session_->ProcessIceMessage(ice_candidate); } bool PeerConnection::RemoveIceCandidates( const std::vector& candidates) { TRACE_EVENT0("webrtc", "PeerConnection::RemoveIceCandidates"); return session_->RemoveRemoteIceCandidates(candidates); } void PeerConnection::RegisterUMAObserver(UMAObserver* observer) { TRACE_EVENT0("webrtc", "PeerConnection::RegisterUmaObserver"); uma_observer_ = observer; if (session_) { session_->set_metrics_observer(uma_observer_); } // Send information about IPv4/IPv6 status. if (uma_observer_ && port_allocator_) { if (port_allocator_->flags() & cricket::PORTALLOCATOR_ENABLE_IPV6) { uma_observer_->IncrementEnumCounter( kEnumCounterAddressFamily, kPeerConnection_IPv6, kPeerConnectionAddressFamilyCounter_Max); } else { uma_observer_->IncrementEnumCounter( kEnumCounterAddressFamily, kPeerConnection_IPv4, kPeerConnectionAddressFamilyCounter_Max); } } } const SessionDescriptionInterface* PeerConnection::local_description() const { return session_->local_description(); } const SessionDescriptionInterface* PeerConnection::remote_description() const { return session_->remote_description(); } void PeerConnection::Close() { TRACE_EVENT0("webrtc", "PeerConnection::Close"); // Update stats here so that we have the most recent stats for tracks and // streams before the channels are closed. stats_->UpdateStats(kStatsOutputLevelStandard); session_->Close(); } void PeerConnection::OnSessionStateChange(WebRtcSession* /*session*/, WebRtcSession::State state) { switch (state) { case WebRtcSession::STATE_INIT: ChangeSignalingState(PeerConnectionInterface::kStable); break; case WebRtcSession::STATE_SENTOFFER: ChangeSignalingState(PeerConnectionInterface::kHaveLocalOffer); break; case WebRtcSession::STATE_SENTPRANSWER: ChangeSignalingState(PeerConnectionInterface::kHaveLocalPrAnswer); break; case WebRtcSession::STATE_RECEIVEDOFFER: ChangeSignalingState(PeerConnectionInterface::kHaveRemoteOffer); break; case WebRtcSession::STATE_RECEIVEDPRANSWER: ChangeSignalingState(PeerConnectionInterface::kHaveRemotePrAnswer); break; case WebRtcSession::STATE_INPROGRESS: ChangeSignalingState(PeerConnectionInterface::kStable); break; case WebRtcSession::STATE_CLOSED: ChangeSignalingState(PeerConnectionInterface::kClosed); break; default: break; } } void PeerConnection::OnMessage(rtc::Message* msg) { switch (msg->message_id) { case MSG_SET_SESSIONDESCRIPTION_SUCCESS: { SetSessionDescriptionMsg* param = static_cast(msg->pdata); param->observer->OnSuccess(); delete param; break; } case MSG_SET_SESSIONDESCRIPTION_FAILED: { SetSessionDescriptionMsg* param = static_cast(msg->pdata); param->observer->OnFailure(param->error); delete param; break; } case MSG_CREATE_SESSIONDESCRIPTION_FAILED: { CreateSessionDescriptionMsg* param = static_cast(msg->pdata); param->observer->OnFailure(param->error); delete param; break; } case MSG_GETSTATS: { GetStatsMsg* param = static_cast(msg->pdata); StatsReports reports; stats_->GetStats(param->track, &reports); param->observer->OnComplete(reports); delete param; break; } case MSG_FREE_DATACHANNELS: { sctp_data_channels_to_free_.clear(); break; } default: RTC_DCHECK(false && "Not implemented"); break; } } void PeerConnection::CreateAudioReceiver(MediaStreamInterface* stream, const std::string& track_id, uint32_t ssrc) { receivers_.push_back( RtpReceiverProxyWithInternal::Create( signaling_thread(), new AudioRtpReceiver(stream, track_id, ssrc, session_.get()))); } void PeerConnection::CreateVideoReceiver(MediaStreamInterface* stream, const std::string& track_id, uint32_t ssrc) { receivers_.push_back( RtpReceiverProxyWithInternal::Create( signaling_thread(), new VideoRtpReceiver(stream, track_id, factory_->worker_thread(), ssrc, session_.get()))); } // TODO(deadbeef): Keep RtpReceivers around even if track goes away in remote // description. void PeerConnection::DestroyReceiver(const std::string& track_id) { auto it = FindReceiverForTrack(track_id); if (it == receivers_.end()) { LOG(LS_WARNING) << "RtpReceiver for track with id " << track_id << " doesn't exist."; } else { (*it)->internal()->Stop(); receivers_.erase(it); } } void PeerConnection::StopReceivers(cricket::MediaType media_type) { TrackInfos* current_tracks = GetRemoteTracks(media_type); for (const auto& track_info : *current_tracks) { auto it = FindReceiverForTrack(track_info.track_id); if (it == receivers_.end()) { LOG(LS_WARNING) << "RtpReceiver for track with id " << track_info.track_id << " doesn't exist."; } else { (*it)->internal()->Stop(); } } } void PeerConnection::OnIceConnectionChange( PeerConnectionInterface::IceConnectionState new_state) { RTC_DCHECK(signaling_thread()->IsCurrent()); // After transitioning to "closed", ignore any additional states from // WebRtcSession (such as "disconnected"). if (IsClosed()) { return; } ice_connection_state_ = new_state; observer_->OnIceConnectionChange(ice_connection_state_); } void PeerConnection::OnIceGatheringChange( PeerConnectionInterface::IceGatheringState new_state) { RTC_DCHECK(signaling_thread()->IsCurrent()); if (IsClosed()) { return; } ice_gathering_state_ = new_state; observer_->OnIceGatheringChange(ice_gathering_state_); } void PeerConnection::OnIceCandidate(const IceCandidateInterface* candidate) { RTC_DCHECK(signaling_thread()->IsCurrent()); observer_->OnIceCandidate(candidate); } void PeerConnection::OnIceCandidatesRemoved( const std::vector& candidates) { RTC_DCHECK(signaling_thread()->IsCurrent()); observer_->OnIceCandidatesRemoved(candidates); } void PeerConnection::OnIceConnectionReceivingChange(bool receiving) { RTC_DCHECK(signaling_thread()->IsCurrent()); observer_->OnIceConnectionReceivingChange(receiving); } void PeerConnection::ChangeSignalingState( PeerConnectionInterface::SignalingState signaling_state) { signaling_state_ = signaling_state; if (signaling_state == kClosed) { ice_connection_state_ = kIceConnectionClosed; observer_->OnIceConnectionChange(ice_connection_state_); if (ice_gathering_state_ != kIceGatheringComplete) { ice_gathering_state_ = kIceGatheringComplete; observer_->OnIceGatheringChange(ice_gathering_state_); } } observer_->OnSignalingChange(signaling_state_); } void PeerConnection::OnAudioTrackAdded(AudioTrackInterface* track, MediaStreamInterface* stream) { auto sender = FindSenderForTrack(track); if (sender != senders_.end()) { // We already have a sender for this track, so just change the stream_id // so that it's correct in the next call to CreateOffer. (*sender)->internal()->set_stream_id(stream->label()); return; } // Normal case; we've never seen this track before. rtc::scoped_refptr> new_sender = RtpSenderProxyWithInternal::Create( signaling_thread(), new AudioRtpSender(track, stream->label(), session_.get(), stats_.get())); senders_.push_back(new_sender); // If the sender has already been configured in SDP, we call SetSsrc, // which will connect the sender to the underlying transport. This can // occur if a local session description that contains the ID of the sender // is set before AddStream is called. It can also occur if the local // session description is not changed and RemoveStream is called, and // later AddStream is called again with the same stream. const TrackInfo* track_info = FindTrackInfo(local_audio_tracks_, stream->label(), track->id()); if (track_info) { new_sender->internal()->SetSsrc(track_info->ssrc); } } // TODO(deadbeef): Don't destroy RtpSenders here; they should be kept around // indefinitely, when we have unified plan SDP. void PeerConnection::OnAudioTrackRemoved(AudioTrackInterface* track, MediaStreamInterface* stream) { auto sender = FindSenderForTrack(track); if (sender == senders_.end()) { LOG(LS_WARNING) << "RtpSender for track with id " << track->id() << " doesn't exist."; return; } (*sender)->internal()->Stop(); senders_.erase(sender); } void PeerConnection::OnVideoTrackAdded(VideoTrackInterface* track, MediaStreamInterface* stream) { auto sender = FindSenderForTrack(track); if (sender != senders_.end()) { // We already have a sender for this track, so just change the stream_id // so that it's correct in the next call to CreateOffer. (*sender)->internal()->set_stream_id(stream->label()); return; } // Normal case; we've never seen this track before. rtc::scoped_refptr> new_sender = RtpSenderProxyWithInternal::Create( signaling_thread(), new VideoRtpSender(track, stream->label(), session_.get())); senders_.push_back(new_sender); const TrackInfo* track_info = FindTrackInfo(local_video_tracks_, stream->label(), track->id()); if (track_info) { new_sender->internal()->SetSsrc(track_info->ssrc); } } void PeerConnection::OnVideoTrackRemoved(VideoTrackInterface* track, MediaStreamInterface* stream) { auto sender = FindSenderForTrack(track); if (sender == senders_.end()) { LOG(LS_WARNING) << "RtpSender for track with id " << track->id() << " doesn't exist."; return; } (*sender)->internal()->Stop(); senders_.erase(sender); } void PeerConnection::PostSetSessionDescriptionFailure( SetSessionDescriptionObserver* observer, const std::string& error) { SetSessionDescriptionMsg* msg = new SetSessionDescriptionMsg(observer); msg->error = error; signaling_thread()->Post(RTC_FROM_HERE, this, MSG_SET_SESSIONDESCRIPTION_FAILED, msg); } void PeerConnection::PostCreateSessionDescriptionFailure( CreateSessionDescriptionObserver* observer, const std::string& error) { CreateSessionDescriptionMsg* msg = new CreateSessionDescriptionMsg(observer); msg->error = error; signaling_thread()->Post(RTC_FROM_HERE, this, MSG_CREATE_SESSIONDESCRIPTION_FAILED, msg); } bool PeerConnection::GetOptionsForOffer( const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options, cricket::MediaSessionOptions* session_options) { // TODO(deadbeef): Once we have transceivers, enumerate them here instead of // ContentInfos. if (session_->local_description()) { for (const cricket::ContentInfo& content : session_->local_description()->description()->contents()) { session_options->transport_options[content.name] = cricket::TransportOptions(); } } if (!ExtractMediaSessionOptions(rtc_options, true, session_options)) { return false; } AddSendStreams(session_options, senders_, rtp_data_channels_); // Offer to receive audio/video if the constraint is not set and there are // send streams, or we're currently receiving. if (rtc_options.offer_to_receive_audio == RTCOfferAnswerOptions::kUndefined) { session_options->recv_audio = session_options->HasSendMediaStream(cricket::MEDIA_TYPE_AUDIO) || !remote_audio_tracks_.empty(); } if (rtc_options.offer_to_receive_video == RTCOfferAnswerOptions::kUndefined) { session_options->recv_video = session_options->HasSendMediaStream(cricket::MEDIA_TYPE_VIDEO) || !remote_video_tracks_.empty(); } session_options->bundle_enabled = session_options->bundle_enabled && (session_options->has_audio() || session_options->has_video() || session_options->has_data()); if (session_->data_channel_type() == cricket::DCT_SCTP && HasDataChannels()) { session_options->data_channel_type = cricket::DCT_SCTP; } session_options->rtcp_cname = rtcp_cname_; return true; } void PeerConnection::FinishOptionsForAnswer( cricket::MediaSessionOptions* session_options) { // TODO(deadbeef): Once we have transceivers, enumerate them here instead of // ContentInfos. if (session_->remote_description()) { // Initialize the transport_options map. for (const cricket::ContentInfo& content : session_->remote_description()->description()->contents()) { session_options->transport_options[content.name] = cricket::TransportOptions(); } } AddSendStreams(session_options, senders_, rtp_data_channels_); session_options->bundle_enabled = session_options->bundle_enabled && (session_options->has_audio() || session_options->has_video() || session_options->has_data()); // RTP data channel is handled in MediaSessionOptions::AddStream. SCTP streams // are not signaled in the SDP so does not go through that path and must be // handled here. if (session_->data_channel_type() == cricket::DCT_SCTP) { session_options->data_channel_type = cricket::DCT_SCTP; } } bool PeerConnection::GetOptionsForAnswer( const MediaConstraintsInterface* constraints, cricket::MediaSessionOptions* session_options) { session_options->recv_audio = false; session_options->recv_video = false; if (!ParseConstraintsForAnswer(constraints, session_options)) { return false; } session_options->rtcp_cname = rtcp_cname_; FinishOptionsForAnswer(session_options); return true; } bool PeerConnection::GetOptionsForAnswer( const RTCOfferAnswerOptions& options, cricket::MediaSessionOptions* session_options) { session_options->recv_audio = false; session_options->recv_video = false; if (!ExtractMediaSessionOptions(options, false, session_options)) { return false; } session_options->rtcp_cname = rtcp_cname_; FinishOptionsForAnswer(session_options); return true; } void PeerConnection::RemoveTracks(cricket::MediaType media_type) { UpdateLocalTracks(std::vector(), media_type); UpdateRemoteStreamsList(std::vector(), false, media_type, nullptr); } void PeerConnection::UpdateRemoteStreamsList( const cricket::StreamParamsVec& streams, bool default_track_needed, cricket::MediaType media_type, StreamCollection* new_streams) { TrackInfos* current_tracks = GetRemoteTracks(media_type); // Find removed tracks. I.e., tracks where the track id or ssrc don't match // the new StreamParam. auto track_it = current_tracks->begin(); while (track_it != current_tracks->end()) { const TrackInfo& info = *track_it; const cricket::StreamParams* params = cricket::GetStreamBySsrc(streams, info.ssrc); bool track_exists = params && params->id == info.track_id; // If this is a default track, and we still need it, don't remove it. if ((info.stream_label == kDefaultStreamLabel && default_track_needed) || track_exists) { ++track_it; } else { OnRemoteTrackRemoved(info.stream_label, info.track_id, media_type); track_it = current_tracks->erase(track_it); } } // Find new and active tracks. for (const cricket::StreamParams& params : streams) { // The sync_label is the MediaStream label and the |stream.id| is the // track id. const std::string& stream_label = params.sync_label; const std::string& track_id = params.id; uint32_t ssrc = params.first_ssrc(); rtc::scoped_refptr stream = remote_streams_->find(stream_label); if (!stream) { // This is a new MediaStream. Create a new remote MediaStream. stream = MediaStreamProxy::Create(rtc::Thread::Current(), MediaStream::Create(stream_label)); remote_streams_->AddStream(stream); new_streams->AddStream(stream); } const TrackInfo* track_info = FindTrackInfo(*current_tracks, stream_label, track_id); if (!track_info) { current_tracks->push_back(TrackInfo(stream_label, track_id, ssrc)); OnRemoteTrackSeen(stream_label, track_id, ssrc, media_type); } } // Add default track if necessary. if (default_track_needed) { rtc::scoped_refptr default_stream = remote_streams_->find(kDefaultStreamLabel); if (!default_stream) { // Create the new default MediaStream. default_stream = MediaStreamProxy::Create( rtc::Thread::Current(), MediaStream::Create(kDefaultStreamLabel)); remote_streams_->AddStream(default_stream); new_streams->AddStream(default_stream); } std::string default_track_id = (media_type == cricket::MEDIA_TYPE_AUDIO) ? kDefaultAudioTrackLabel : kDefaultVideoTrackLabel; const TrackInfo* default_track_info = FindTrackInfo(*current_tracks, kDefaultStreamLabel, default_track_id); if (!default_track_info) { current_tracks->push_back( TrackInfo(kDefaultStreamLabel, default_track_id, 0)); OnRemoteTrackSeen(kDefaultStreamLabel, default_track_id, 0, media_type); } } } void PeerConnection::OnRemoteTrackSeen(const std::string& stream_label, const std::string& track_id, uint32_t ssrc, cricket::MediaType media_type) { MediaStreamInterface* stream = remote_streams_->find(stream_label); if (media_type == cricket::MEDIA_TYPE_AUDIO) { CreateAudioReceiver(stream, track_id, ssrc); } else if (media_type == cricket::MEDIA_TYPE_VIDEO) { CreateVideoReceiver(stream, track_id, ssrc); } else { RTC_DCHECK(false && "Invalid media type"); } } void PeerConnection::OnRemoteTrackRemoved(const std::string& stream_label, const std::string& track_id, cricket::MediaType media_type) { MediaStreamInterface* stream = remote_streams_->find(stream_label); if (media_type == cricket::MEDIA_TYPE_AUDIO) { // When the MediaEngine audio channel is destroyed, the RemoteAudioSource // will be notified which will end the AudioRtpReceiver::track(). DestroyReceiver(track_id); rtc::scoped_refptr audio_track = stream->FindAudioTrack(track_id); if (audio_track) { stream->RemoveTrack(audio_track); } } else if (media_type == cricket::MEDIA_TYPE_VIDEO) { // Stopping or destroying a VideoRtpReceiver will end the // VideoRtpReceiver::track(). DestroyReceiver(track_id); rtc::scoped_refptr video_track = stream->FindVideoTrack(track_id); if (video_track) { // There's no guarantee the track is still available, e.g. the track may // have been removed from the stream by an application. stream->RemoveTrack(video_track); } } else { ASSERT(false && "Invalid media type"); } } void PeerConnection::UpdateEndedRemoteMediaStreams() { std::vector> streams_to_remove; for (size_t i = 0; i < remote_streams_->count(); ++i) { MediaStreamInterface* stream = remote_streams_->at(i); if (stream->GetAudioTracks().empty() && stream->GetVideoTracks().empty()) { streams_to_remove.push_back(stream); } } for (auto& stream : streams_to_remove) { remote_streams_->RemoveStream(stream); // Call both the raw pointer and scoped_refptr versions of the method // for compatibility. observer_->OnRemoveStream(stream.get()); observer_->OnRemoveStream(std::move(stream)); } } void PeerConnection::UpdateLocalTracks( const std::vector& streams, cricket::MediaType media_type) { TrackInfos* current_tracks = GetLocalTracks(media_type); // Find removed tracks. I.e., tracks where the track id, stream label or ssrc // don't match the new StreamParam. TrackInfos::iterator track_it = current_tracks->begin(); while (track_it != current_tracks->end()) { const TrackInfo& info = *track_it; const cricket::StreamParams* params = cricket::GetStreamBySsrc(streams, info.ssrc); if (!params || params->id != info.track_id || params->sync_label != info.stream_label) { OnLocalTrackRemoved(info.stream_label, info.track_id, info.ssrc, media_type); track_it = current_tracks->erase(track_it); } else { ++track_it; } } // Find new and active tracks. for (const cricket::StreamParams& params : streams) { // The sync_label is the MediaStream label and the |stream.id| is the // track id. const std::string& stream_label = params.sync_label; const std::string& track_id = params.id; uint32_t ssrc = params.first_ssrc(); const TrackInfo* track_info = FindTrackInfo(*current_tracks, stream_label, track_id); if (!track_info) { current_tracks->push_back(TrackInfo(stream_label, track_id, ssrc)); OnLocalTrackSeen(stream_label, track_id, params.first_ssrc(), media_type); } } } void PeerConnection::OnLocalTrackSeen(const std::string& stream_label, const std::string& track_id, uint32_t ssrc, cricket::MediaType media_type) { RtpSenderInternal* sender = FindSenderById(track_id); if (!sender) { LOG(LS_WARNING) << "An unknown RtpSender with id " << track_id << " has been configured in the local description."; return; } if (sender->media_type() != media_type) { LOG(LS_WARNING) << "An RtpSender has been configured in the local" << " description with an unexpected media type."; return; } sender->set_stream_id(stream_label); sender->SetSsrc(ssrc); } void PeerConnection::OnLocalTrackRemoved(const std::string& stream_label, const std::string& track_id, uint32_t ssrc, cricket::MediaType media_type) { RtpSenderInternal* sender = FindSenderById(track_id); if (!sender) { // This is the normal case. I.e., RemoveStream has been called and the // SessionDescriptions has been renegotiated. return; } // A sender has been removed from the SessionDescription but it's still // associated with the PeerConnection. This only occurs if the SDP doesn't // match with the calls to CreateSender, AddStream and RemoveStream. if (sender->media_type() != media_type) { LOG(LS_WARNING) << "An RtpSender has been configured in the local" << " description with an unexpected media type."; return; } sender->SetSsrc(0); } void PeerConnection::UpdateLocalRtpDataChannels( const cricket::StreamParamsVec& streams) { std::vector existing_channels; // Find new and active data channels. for (const cricket::StreamParams& params : streams) { // |it->sync_label| is actually the data channel label. The reason is that // we use the same naming of data channels as we do for // MediaStreams and Tracks. // For MediaStreams, the sync_label is the MediaStream label and the // track label is the same as |streamid|. const std::string& channel_label = params.sync_label; auto data_channel_it = rtp_data_channels_.find(channel_label); if (!VERIFY(data_channel_it != rtp_data_channels_.end())) { continue; } // Set the SSRC the data channel should use for sending. data_channel_it->second->SetSendSsrc(params.first_ssrc()); existing_channels.push_back(data_channel_it->first); } UpdateClosingRtpDataChannels(existing_channels, true); } void PeerConnection::UpdateRemoteRtpDataChannels( const cricket::StreamParamsVec& streams) { std::vector existing_channels; // Find new and active data channels. for (const cricket::StreamParams& params : streams) { // The data channel label is either the mslabel or the SSRC if the mslabel // does not exist. Ex a=ssrc:444330170 mslabel:test1. std::string label = params.sync_label.empty() ? rtc::ToString(params.first_ssrc()) : params.sync_label; auto data_channel_it = rtp_data_channels_.find(label); if (data_channel_it == rtp_data_channels_.end()) { // This is a new data channel. CreateRemoteRtpDataChannel(label, params.first_ssrc()); } else { data_channel_it->second->SetReceiveSsrc(params.first_ssrc()); } existing_channels.push_back(label); } UpdateClosingRtpDataChannels(existing_channels, false); } void PeerConnection::UpdateClosingRtpDataChannels( const std::vector& active_channels, bool is_local_update) { auto it = rtp_data_channels_.begin(); while (it != rtp_data_channels_.end()) { DataChannel* data_channel = it->second; if (std::find(active_channels.begin(), active_channels.end(), data_channel->label()) != active_channels.end()) { ++it; continue; } if (is_local_update) { data_channel->SetSendSsrc(0); } else { data_channel->RemotePeerRequestClose(); } if (data_channel->state() == DataChannel::kClosed) { rtp_data_channels_.erase(it); it = rtp_data_channels_.begin(); } else { ++it; } } } void PeerConnection::CreateRemoteRtpDataChannel(const std::string& label, uint32_t remote_ssrc) { rtc::scoped_refptr channel( InternalCreateDataChannel(label, nullptr)); if (!channel.get()) { LOG(LS_WARNING) << "Remote peer requested a DataChannel but" << "CreateDataChannel failed."; return; } channel->SetReceiveSsrc(remote_ssrc); rtc::scoped_refptr proxy_channel = DataChannelProxy::Create(signaling_thread(), channel); // Call both the raw pointer and scoped_refptr versions of the method // for compatibility. observer_->OnDataChannel(proxy_channel.get()); observer_->OnDataChannel(std::move(proxy_channel)); } rtc::scoped_refptr PeerConnection::InternalCreateDataChannel( const std::string& label, const InternalDataChannelInit* config) { if (IsClosed()) { return nullptr; } if (session_->data_channel_type() == cricket::DCT_NONE) { LOG(LS_ERROR) << "InternalCreateDataChannel: Data is not supported in this call."; return nullptr; } InternalDataChannelInit new_config = config ? (*config) : InternalDataChannelInit(); if (session_->data_channel_type() == cricket::DCT_SCTP) { if (new_config.id < 0) { rtc::SSLRole role; if ((session_->GetSslRole(session_->data_channel(), &role)) && !sid_allocator_.AllocateSid(role, &new_config.id)) { LOG(LS_ERROR) << "No id can be allocated for the SCTP data channel."; return nullptr; } } else if (!sid_allocator_.ReserveSid(new_config.id)) { LOG(LS_ERROR) << "Failed to create a SCTP data channel " << "because the id is already in use or out of range."; return nullptr; } } rtc::scoped_refptr channel(DataChannel::Create( session_.get(), session_->data_channel_type(), label, new_config)); if (!channel) { sid_allocator_.ReleaseSid(new_config.id); return nullptr; } if (channel->data_channel_type() == cricket::DCT_RTP) { if (rtp_data_channels_.find(channel->label()) != rtp_data_channels_.end()) { LOG(LS_ERROR) << "DataChannel with label " << channel->label() << " already exists."; return nullptr; } rtp_data_channels_[channel->label()] = channel; } else { RTC_DCHECK(channel->data_channel_type() == cricket::DCT_SCTP); sctp_data_channels_.push_back(channel); channel->SignalClosed.connect(this, &PeerConnection::OnSctpDataChannelClosed); } return channel; } bool PeerConnection::HasDataChannels() const { return !rtp_data_channels_.empty() || !sctp_data_channels_.empty(); } void PeerConnection::AllocateSctpSids(rtc::SSLRole role) { for (const auto& channel : sctp_data_channels_) { if (channel->id() < 0) { int sid; if (!sid_allocator_.AllocateSid(role, &sid)) { LOG(LS_ERROR) << "Failed to allocate SCTP sid."; continue; } channel->SetSctpSid(sid); } } } void PeerConnection::OnSctpDataChannelClosed(DataChannel* channel) { RTC_DCHECK(signaling_thread()->IsCurrent()); for (auto it = sctp_data_channels_.begin(); it != sctp_data_channels_.end(); ++it) { if (it->get() == channel) { if (channel->id() >= 0) { sid_allocator_.ReleaseSid(channel->id()); } // Since this method is triggered by a signal from the DataChannel, // we can't free it directly here; we need to free it asynchronously. sctp_data_channels_to_free_.push_back(*it); sctp_data_channels_.erase(it); signaling_thread()->Post(RTC_FROM_HERE, this, MSG_FREE_DATACHANNELS, nullptr); return; } } } void PeerConnection::OnVoiceChannelDestroyed() { StopReceivers(cricket::MEDIA_TYPE_AUDIO); } void PeerConnection::OnVideoChannelDestroyed() { StopReceivers(cricket::MEDIA_TYPE_VIDEO); } void PeerConnection::OnDataChannelCreated() { for (const auto& channel : sctp_data_channels_) { channel->OnTransportChannelCreated(); } } void PeerConnection::OnDataChannelDestroyed() { // Use a temporary copy of the RTP/SCTP DataChannel list because the // DataChannel may callback to us and try to modify the list. std::map> temp_rtp_dcs; temp_rtp_dcs.swap(rtp_data_channels_); for (const auto& kv : temp_rtp_dcs) { kv.second->OnTransportChannelDestroyed(); } std::vector> temp_sctp_dcs; temp_sctp_dcs.swap(sctp_data_channels_); for (const auto& channel : temp_sctp_dcs) { channel->OnTransportChannelDestroyed(); } } void PeerConnection::OnDataChannelOpenMessage( const std::string& label, const InternalDataChannelInit& config) { rtc::scoped_refptr channel( InternalCreateDataChannel(label, &config)); if (!channel.get()) { LOG(LS_ERROR) << "Failed to create DataChannel from the OPEN message."; return; } rtc::scoped_refptr proxy_channel = DataChannelProxy::Create(signaling_thread(), channel); // Call both the raw pointer and scoped_refptr versions of the method // for compatibility. observer_->OnDataChannel(proxy_channel.get()); observer_->OnDataChannel(std::move(proxy_channel)); } RtpSenderInternal* PeerConnection::FindSenderById(const std::string& id) { auto it = std::find_if( senders_.begin(), senders_.end(), [id](const rtc::scoped_refptr< RtpSenderProxyWithInternal>& sender) { return sender->id() == id; }); return it != senders_.end() ? (*it)->internal() : nullptr; } std::vector< rtc::scoped_refptr>>::iterator PeerConnection::FindSenderForTrack(MediaStreamTrackInterface* track) { return std::find_if( senders_.begin(), senders_.end(), [track](const rtc::scoped_refptr< RtpSenderProxyWithInternal>& sender) { return sender->track() == track; }); } std::vector>>::iterator PeerConnection::FindReceiverForTrack(const std::string& track_id) { return std::find_if( receivers_.begin(), receivers_.end(), [track_id](const rtc::scoped_refptr< RtpReceiverProxyWithInternal>& receiver) { return receiver->id() == track_id; }); } PeerConnection::TrackInfos* PeerConnection::GetRemoteTracks( cricket::MediaType media_type) { RTC_DCHECK(media_type == cricket::MEDIA_TYPE_AUDIO || media_type == cricket::MEDIA_TYPE_VIDEO); return (media_type == cricket::MEDIA_TYPE_AUDIO) ? &remote_audio_tracks_ : &remote_video_tracks_; } PeerConnection::TrackInfos* PeerConnection::GetLocalTracks( cricket::MediaType media_type) { RTC_DCHECK(media_type == cricket::MEDIA_TYPE_AUDIO || media_type == cricket::MEDIA_TYPE_VIDEO); return (media_type == cricket::MEDIA_TYPE_AUDIO) ? &local_audio_tracks_ : &local_video_tracks_; } const PeerConnection::TrackInfo* PeerConnection::FindTrackInfo( const PeerConnection::TrackInfos& infos, const std::string& stream_label, const std::string track_id) const { for (const TrackInfo& track_info : infos) { if (track_info.stream_label == stream_label && track_info.track_id == track_id) { return &track_info; } } return nullptr; } DataChannel* PeerConnection::FindDataChannelBySid(int sid) const { for (const auto& channel : sctp_data_channels_) { if (channel->id() == sid) { return channel; } } return nullptr; } bool PeerConnection::InitializePortAllocator_n( const RTCConfiguration& configuration) { cricket::ServerAddresses stun_servers; std::vector turn_servers; if (!ParseIceServers(configuration.servers, &stun_servers, &turn_servers)) { return false; } // To handle both internal and externally created port allocator, we will // enable BUNDLE here. int portallocator_flags = port_allocator_->flags(); portallocator_flags |= cricket::PORTALLOCATOR_ENABLE_SHARED_SOCKET | cricket::PORTALLOCATOR_ENABLE_IPV6; // If the disable-IPv6 flag was specified, we'll not override it // by experiment. if (configuration.disable_ipv6) { portallocator_flags &= ~(cricket::PORTALLOCATOR_ENABLE_IPV6); } else if (webrtc::field_trial::FindFullName("WebRTC-IPv6Default") == "Disabled") { portallocator_flags &= ~(cricket::PORTALLOCATOR_ENABLE_IPV6); } if (configuration.tcp_candidate_policy == kTcpCandidatePolicyDisabled) { portallocator_flags |= cricket::PORTALLOCATOR_DISABLE_TCP; LOG(LS_INFO) << "TCP candidates are disabled."; } if (configuration.candidate_network_policy == kCandidateNetworkPolicyLowCost) { portallocator_flags |= cricket::PORTALLOCATOR_DISABLE_COSTLY_NETWORKS; LOG(LS_INFO) << "Do not gather candidates on high-cost networks"; } port_allocator_->set_flags(portallocator_flags); // No step delay is used while allocating ports. port_allocator_->set_step_delay(cricket::kMinimumStepDelay); port_allocator_->set_candidate_filter( ConvertIceTransportTypeToCandidateFilter(configuration.type)); // Call this last since it may create pooled allocator sessions using the // properties set above. port_allocator_->SetConfiguration(stun_servers, turn_servers, configuration.ice_candidate_pool_size); return true; } bool PeerConnection::ReconfigurePortAllocator_n( const RTCConfiguration& configuration) { cricket::ServerAddresses stun_servers; std::vector turn_servers; if (!ParseIceServers(configuration.servers, &stun_servers, &turn_servers)) { return false; } port_allocator_->set_candidate_filter( ConvertIceTransportTypeToCandidateFilter(configuration.type)); // Call this last since it may create pooled allocator sessions using the // candidate filter set above. port_allocator_->SetConfiguration(stun_servers, turn_servers, configuration.ice_candidate_pool_size); return true; } } // namespace webrtc