OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "remoting/protocol/webrtc_transport.h" |
| 6 |
| 7 #include "base/callback_helpers.h" |
| 8 #include "base/single_thread_task_runner.h" |
| 9 #include "base/strings/string_number_conversions.h" |
| 10 #include "base/task_runner_util.h" |
| 11 #include "jingle/glue/thread_wrapper.h" |
| 12 #include "third_party/libjingle/source/talk/app/webrtc/test/fakeconstraints.h" |
| 13 #include "third_party/webrtc/libjingle/xmllite/xmlelement.h" |
| 14 #include "third_party/webrtc/modules/audio_device/include/fake_audio_device.h" |
| 15 |
| 16 using buzz::QName; |
| 17 using buzz::XmlElement; |
| 18 |
| 19 namespace remoting { |
| 20 namespace protocol { |
| 21 |
| 22 namespace { |
| 23 |
| 24 // Delay after candidate creation before sending transport-info message to |
| 25 // accumulate multiple candidates. This is an optimization to reduce number of |
| 26 // transport-info messages. |
| 27 const int kTransportInfoSendDelayMs = 20; |
| 28 |
| 29 // XML namespace for the transport elements. |
| 30 const char kTransportNamespace[] = "google:remoting:webrtc"; |
| 31 |
| 32 rtc::Thread* InitAndGetRtcThread() { |
| 33 jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop(); |
| 34 |
| 35 // TODO(sergeyu): Investigate if it's possible to avoid Send(). |
| 36 jingle_glue::JingleThreadWrapper::current()->set_send_allowed(true); |
| 37 |
| 38 return jingle_glue::JingleThreadWrapper::current(); |
| 39 } |
| 40 |
| 41 // A webrtc::CreateSessionDescriptionObserver implementation used to receive the |
| 42 // results of creating descriptions for this end of the PeerConnection. |
| 43 class CreateSessionDescriptionObserver |
| 44 : public webrtc::CreateSessionDescriptionObserver { |
| 45 public: |
| 46 typedef base::Callback<void( |
| 47 scoped_ptr<webrtc::SessionDescriptionInterface> description, |
| 48 const std::string& error)> ResultCallback; |
| 49 |
| 50 static CreateSessionDescriptionObserver* Create( |
| 51 const ResultCallback& result_callback) { |
| 52 return new rtc::RefCountedObject<CreateSessionDescriptionObserver>( |
| 53 result_callback); |
| 54 } |
| 55 void OnSuccess(webrtc::SessionDescriptionInterface* desc) override { |
| 56 base::ResetAndReturn(&result_callback_) |
| 57 .Run(make_scoped_ptr(desc), std::string()); |
| 58 } |
| 59 void OnFailure(const std::string& error) override { |
| 60 base::ResetAndReturn(&result_callback_).Run(nullptr, error); |
| 61 } |
| 62 |
| 63 protected: |
| 64 explicit CreateSessionDescriptionObserver( |
| 65 const ResultCallback& result_callback) |
| 66 : result_callback_(result_callback) {} |
| 67 ~CreateSessionDescriptionObserver() override {} |
| 68 |
| 69 private: |
| 70 ResultCallback result_callback_; |
| 71 |
| 72 DISALLOW_COPY_AND_ASSIGN(CreateSessionDescriptionObserver); |
| 73 }; |
| 74 |
| 75 // A webrtc::SetSessionDescriptionObserver implementation used to receive the |
| 76 // results of setting local and remote descriptions of the PeerConnection. |
| 77 class SetSessionDescriptionObserver |
| 78 : public webrtc::SetSessionDescriptionObserver { |
| 79 public: |
| 80 typedef base::Callback<void(bool success, const std::string& error)> |
| 81 ResultCallback; |
| 82 |
| 83 static SetSessionDescriptionObserver* Create( |
| 84 const ResultCallback& result_callback) { |
| 85 return new rtc::RefCountedObject<SetSessionDescriptionObserver>( |
| 86 result_callback); |
| 87 } |
| 88 |
| 89 void OnSuccess() override { |
| 90 base::ResetAndReturn(&result_callback_).Run(true, std::string()); |
| 91 } |
| 92 |
| 93 void OnFailure(const std::string& error) override { |
| 94 base::ResetAndReturn(&result_callback_).Run(false, error); |
| 95 } |
| 96 |
| 97 protected: |
| 98 SetSessionDescriptionObserver(const ResultCallback& result_callback) |
| 99 : result_callback_(result_callback) {} |
| 100 ~SetSessionDescriptionObserver() override {} |
| 101 |
| 102 private: |
| 103 ResultCallback result_callback_; |
| 104 |
| 105 DISALLOW_COPY_AND_ASSIGN(SetSessionDescriptionObserver); |
| 106 }; |
| 107 |
| 108 } // namespace |
| 109 |
| 110 WebrtcTransport::WebrtcTransport( |
| 111 rtc::scoped_refptr<webrtc::PortAllocatorFactoryInterface> |
| 112 port_allocator_factory, |
| 113 TransportRole role, |
| 114 scoped_refptr<base::SingleThreadTaskRunner> worker_task_runner) |
| 115 : port_allocator_factory_(port_allocator_factory), |
| 116 role_(role), |
| 117 worker_task_runner_(worker_task_runner), |
| 118 weak_factory_(this) {} |
| 119 |
| 120 WebrtcTransport::~WebrtcTransport() {} |
| 121 |
| 122 void WebrtcTransport::Start(EventHandler* event_handler, |
| 123 Authenticator* authenticator) { |
| 124 DCHECK(thread_checker_.CalledOnValidThread()); |
| 125 |
| 126 event_handler_ = event_handler; |
| 127 |
| 128 // TODO(sergeyu): Use the |authenticator| to authenticate PeerConnection. |
| 129 |
| 130 base::PostTaskAndReplyWithResult( |
| 131 worker_task_runner_.get(), FROM_HERE, base::Bind(&InitAndGetRtcThread), |
| 132 base::Bind(&WebrtcTransport::DoStart, weak_factory_.GetWeakPtr())); |
| 133 } |
| 134 |
| 135 bool WebrtcTransport::ProcessTransportInfo(XmlElement* transport_info) { |
| 136 DCHECK(thread_checker_.CalledOnValidThread()); |
| 137 |
| 138 if (transport_info->Name() != QName(kTransportNamespace, "transport")) |
| 139 return false; |
| 140 |
| 141 if (!peer_connection_) |
| 142 return false; |
| 143 |
| 144 XmlElement* session_description = transport_info->FirstNamed( |
| 145 QName(kTransportNamespace, "session-description")); |
| 146 if (session_description) { |
| 147 webrtc::PeerConnectionInterface::SignalingState expected_state = |
| 148 role_ == TransportRole::SERVER |
| 149 ? webrtc::PeerConnectionInterface::kStable |
| 150 : webrtc::PeerConnectionInterface::kHaveLocalOffer; |
| 151 if (peer_connection_->signaling_state() != expected_state) { |
| 152 LOG(ERROR) << "Received unexpected WebRTC session_description. "; |
| 153 return false; |
| 154 } |
| 155 |
| 156 std::string type = session_description->Attr(QName(std::string(), "type")); |
| 157 std::string sdp = session_description->BodyText(); |
| 158 if (type.empty() || sdp.empty()) { |
| 159 LOG(ERROR) << "Incorrect session_description format."; |
| 160 return false; |
| 161 } |
| 162 |
| 163 webrtc::SdpParseError error; |
| 164 scoped_ptr<webrtc::SessionDescriptionInterface> session_description( |
| 165 webrtc::CreateSessionDescription(type, sdp, &error)); |
| 166 if (!session_description) { |
| 167 LOG(ERROR) << "Failed to parse the offer: " << error.description |
| 168 << " line: " << error.line; |
| 169 return false; |
| 170 } |
| 171 |
| 172 peer_connection_->SetRemoteDescription( |
| 173 SetSessionDescriptionObserver::Create( |
| 174 base::Bind(&WebrtcTransport::OnRemoteDescriptionSet, |
| 175 weak_factory_.GetWeakPtr())), |
| 176 session_description.release()); |
| 177 } |
| 178 |
| 179 XmlElement* candidate_element; |
| 180 QName candidate_qname(kTransportNamespace, "candidate"); |
| 181 for (candidate_element = transport_info->FirstNamed(candidate_qname); |
| 182 candidate_element; |
| 183 candidate_element = candidate_element->NextNamed(candidate_qname)) { |
| 184 std::string candidate_str = candidate_element->BodyText(); |
| 185 std::string sdp_mid = |
| 186 candidate_element->Attr(QName(std::string(), "sdpMid")); |
| 187 std::string sdp_mlineindex_str = |
| 188 candidate_element->Attr(QName(std::string(), "sdpMLineIndex")); |
| 189 int sdp_mlineindex; |
| 190 if (candidate_str.empty() || sdp_mid.empty() || |
| 191 !base::StringToInt(sdp_mlineindex_str, &sdp_mlineindex)) { |
| 192 LOG(ERROR) << "Failed to parse incoming candidates."; |
| 193 return false; |
| 194 } |
| 195 |
| 196 webrtc::SdpParseError error; |
| 197 scoped_ptr<webrtc::IceCandidateInterface> candidate( |
| 198 webrtc::CreateIceCandidate(sdp_mid, sdp_mlineindex, candidate_str, |
| 199 &error)); |
| 200 if (!candidate) { |
| 201 LOG(ERROR) << "Failed to parse incoming candidate: " << error.description |
| 202 << " line: " << error.line; |
| 203 return false; |
| 204 } |
| 205 |
| 206 if (peer_connection_->signaling_state() == |
| 207 webrtc::PeerConnectionInterface::kStable) { |
| 208 if (!peer_connection_->AddIceCandidate(candidate.get())) { |
| 209 LOG(ERROR) << "Failed to add incoming ICE candidate."; |
| 210 return false; |
| 211 } |
| 212 } else { |
| 213 pending_incoming_candidates_.push_back(candidate.Pass()); |
| 214 } |
| 215 } |
| 216 |
| 217 return true; |
| 218 } |
| 219 |
| 220 DatagramChannelFactory* WebrtcTransport::GetDatagramChannelFactory() { |
| 221 DCHECK(thread_checker_.CalledOnValidThread()); |
| 222 NOTIMPLEMENTED(); |
| 223 return nullptr; |
| 224 } |
| 225 |
| 226 StreamChannelFactory* WebrtcTransport::GetStreamChannelFactory() { |
| 227 DCHECK(thread_checker_.CalledOnValidThread()); |
| 228 // TODO(sergeyu): Implement data stream support. |
| 229 NOTIMPLEMENTED(); |
| 230 return nullptr; |
| 231 } |
| 232 |
| 233 StreamChannelFactory* WebrtcTransport::GetMultiplexedChannelFactory() { |
| 234 DCHECK(thread_checker_.CalledOnValidThread()); |
| 235 return GetStreamChannelFactory(); |
| 236 } |
| 237 |
| 238 void WebrtcTransport::DoStart(rtc::Thread* worker_thread) { |
| 239 DCHECK(thread_checker_.CalledOnValidThread()); |
| 240 |
| 241 jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop(); |
| 242 |
| 243 // TODO(sergeyu): Investigate if it's possible to avoid Send(). |
| 244 jingle_glue::JingleThreadWrapper::current()->set_send_allowed(true); |
| 245 |
| 246 fake_audio_device_module_.reset(new webrtc::FakeAudioDeviceModule()); |
| 247 |
| 248 peer_connection_factory_ = webrtc::CreatePeerConnectionFactory( |
| 249 worker_thread, rtc::Thread::Current(), |
| 250 fake_audio_device_module_.get(), nullptr, nullptr); |
| 251 |
| 252 webrtc::PeerConnectionInterface::IceServer stun_server; |
| 253 stun_server.urls.push_back("stun:stun.l.google.com:19302"); |
| 254 webrtc::PeerConnectionInterface::RTCConfiguration rtc_config; |
| 255 rtc_config.servers.push_back(stun_server); |
| 256 |
| 257 webrtc::FakeConstraints constraints; |
| 258 constraints.AddMandatory(webrtc::MediaConstraintsInterface::kEnableDtlsSrtp, |
| 259 webrtc::MediaConstraintsInterface::kValueTrue); |
| 260 |
| 261 peer_connection_ = peer_connection_factory_->CreatePeerConnection( |
| 262 rtc_config, &constraints, port_allocator_factory_, nullptr, this); |
| 263 |
| 264 if (role_ == TransportRole::CLIENT) { |
| 265 webrtc::FakeConstraints offer_config; |
| 266 offer_config.AddMandatory( |
| 267 webrtc::MediaConstraintsInterface::kOfferToReceiveVideo, |
| 268 webrtc::MediaConstraintsInterface::kValueTrue); |
| 269 offer_config.AddMandatory( |
| 270 webrtc::MediaConstraintsInterface::kOfferToReceiveAudio, |
| 271 webrtc::MediaConstraintsInterface::kValueFalse); |
| 272 peer_connection_->CreateOffer( |
| 273 CreateSessionDescriptionObserver::Create( |
| 274 base::Bind(&WebrtcTransport::OnLocalSessionDescriptionCreated, |
| 275 weak_factory_.GetWeakPtr())), |
| 276 &offer_config); |
| 277 } |
| 278 } |
| 279 |
| 280 void WebrtcTransport::OnLocalSessionDescriptionCreated( |
| 281 scoped_ptr<webrtc::SessionDescriptionInterface> description, |
| 282 const std::string& error) { |
| 283 DCHECK(thread_checker_.CalledOnValidThread()); |
| 284 |
| 285 if (!peer_connection_) |
| 286 return; |
| 287 |
| 288 if (!description) { |
| 289 LOG(ERROR) << "PeerConnection offer creation failed: " << error; |
| 290 Close(CHANNEL_CONNECTION_ERROR); |
| 291 return; |
| 292 } |
| 293 |
| 294 std::string description_sdp; |
| 295 if (!description->ToString(&description_sdp)) { |
| 296 LOG(ERROR) << "Failed to serialize description."; |
| 297 Close(CHANNEL_CONNECTION_ERROR); |
| 298 return; |
| 299 } |
| 300 |
| 301 // Format and send the session description to the peer. |
| 302 scoped_ptr<XmlElement> transport_info( |
| 303 new XmlElement(QName(kTransportNamespace, "transport"), true)); |
| 304 XmlElement* offer_tag = |
| 305 new XmlElement(QName(kTransportNamespace, "session-description")); |
| 306 transport_info->AddElement(offer_tag); |
| 307 offer_tag->SetAttr(QName(std::string(), "type"), description->type()); |
| 308 offer_tag->SetBodyText(description_sdp); |
| 309 |
| 310 event_handler_->OnOutgoingTransportInfo(transport_info.Pass()); |
| 311 |
| 312 peer_connection_->SetLocalDescription( |
| 313 SetSessionDescriptionObserver::Create(base::Bind( |
| 314 &WebrtcTransport::OnLocalDescriptionSet, weak_factory_.GetWeakPtr())), |
| 315 description.release()); |
| 316 } |
| 317 |
| 318 void WebrtcTransport::OnLocalDescriptionSet(bool success, |
| 319 const std::string& error) { |
| 320 DCHECK(thread_checker_.CalledOnValidThread()); |
| 321 |
| 322 if (!peer_connection_) |
| 323 return; |
| 324 |
| 325 if (!success) { |
| 326 LOG(ERROR) << "Failed to set local description: " << error; |
| 327 Close(CHANNEL_CONNECTION_ERROR); |
| 328 return; |
| 329 } |
| 330 |
| 331 AddPendingCandidatesIfPossible(); |
| 332 } |
| 333 |
| 334 void WebrtcTransport::OnRemoteDescriptionSet(bool success, |
| 335 const std::string& error) { |
| 336 DCHECK(thread_checker_.CalledOnValidThread()); |
| 337 |
| 338 if (!peer_connection_) |
| 339 return; |
| 340 |
| 341 if (!success) { |
| 342 LOG(ERROR) << "Failed to set local description: " << error; |
| 343 Close(CHANNEL_CONNECTION_ERROR); |
| 344 return; |
| 345 } |
| 346 |
| 347 // Create and send answer on the server. |
| 348 if (role_ == TransportRole::SERVER) { |
| 349 peer_connection_->CreateAnswer( |
| 350 CreateSessionDescriptionObserver::Create( |
| 351 base::Bind(&WebrtcTransport::OnLocalSessionDescriptionCreated, |
| 352 weak_factory_.GetWeakPtr())), |
| 353 nullptr); |
| 354 } |
| 355 |
| 356 AddPendingCandidatesIfPossible(); |
| 357 } |
| 358 |
| 359 void WebrtcTransport::Close(ErrorCode error) { |
| 360 DCHECK(thread_checker_.CalledOnValidThread()); |
| 361 |
| 362 weak_factory_.InvalidateWeakPtrs(); |
| 363 peer_connection_->Close(); |
| 364 peer_connection_ = nullptr; |
| 365 peer_connection_factory_ = nullptr; |
| 366 |
| 367 if (error != OK) |
| 368 event_handler_->OnTransportError(error); |
| 369 } |
| 370 |
| 371 void WebrtcTransport::OnSignalingChange( |
| 372 webrtc::PeerConnectionInterface::SignalingState new_state) { |
| 373 DCHECK(thread_checker_.CalledOnValidThread()); |
| 374 } |
| 375 |
| 376 void WebrtcTransport::OnAddStream(webrtc::MediaStreamInterface* stream) { |
| 377 DCHECK(thread_checker_.CalledOnValidThread()); |
| 378 LOG(ERROR) << "Stream added " << stream->label(); |
| 379 } |
| 380 |
| 381 void WebrtcTransport::OnRemoveStream(webrtc::MediaStreamInterface* stream) { |
| 382 DCHECK(thread_checker_.CalledOnValidThread()); |
| 383 LOG(ERROR) << "Stream removed " << stream->label(); |
| 384 } |
| 385 |
| 386 void WebrtcTransport::OnDataChannel( |
| 387 webrtc::DataChannelInterface* data_channel) { |
| 388 DCHECK(thread_checker_.CalledOnValidThread()); |
| 389 // TODO(sergeyu): Use the data channel. |
| 390 } |
| 391 |
| 392 void WebrtcTransport::OnRenegotiationNeeded() { |
| 393 DCHECK(thread_checker_.CalledOnValidThread()); |
| 394 // TODO(sergeyu): Figure out what needs to happen here. |
| 395 } |
| 396 |
| 397 void WebrtcTransport::OnIceConnectionChange( |
| 398 webrtc::PeerConnectionInterface::IceConnectionState new_state) { |
| 399 DCHECK(thread_checker_.CalledOnValidThread()); |
| 400 |
| 401 if (new_state == webrtc::PeerConnectionInterface::kIceConnectionConnected) |
| 402 event_handler_->OnTransportConnected(); |
| 403 } |
| 404 |
| 405 void WebrtcTransport::OnIceGatheringChange( |
| 406 webrtc::PeerConnectionInterface::IceGatheringState new_state) { |
| 407 DCHECK(thread_checker_.CalledOnValidThread()); |
| 408 } |
| 409 |
| 410 void WebrtcTransport::OnIceCandidate( |
| 411 const webrtc::IceCandidateInterface* candidate) { |
| 412 DCHECK(thread_checker_.CalledOnValidThread()); |
| 413 |
| 414 scoped_ptr<XmlElement> candidate_element( |
| 415 new XmlElement(QName(kTransportNamespace, "candidate"))); |
| 416 std::string candidate_str; |
| 417 if (!candidate->ToString(&candidate_str)) { |
| 418 LOG(ERROR) << "Failed to serialize local candidate."; |
| 419 return; |
| 420 } |
| 421 candidate_element->SetBodyText(candidate_str); |
| 422 candidate_element->SetAttr(QName(std::string(), "sdpMid"), |
| 423 candidate->sdp_mid()); |
| 424 candidate_element->SetAttr(QName(std::string(), "sdpMLineIndex"), |
| 425 base::IntToString(candidate->sdp_mline_index())); |
| 426 |
| 427 EnsurePendingTransportInfoMessage(); |
| 428 pending_transport_info_message_->AddElement(candidate_element.release()); |
| 429 } |
| 430 |
| 431 void WebrtcTransport::EnsurePendingTransportInfoMessage() { |
| 432 DCHECK(thread_checker_.CalledOnValidThread()); |
| 433 |
| 434 // |transport_info_timer_| must be running iff |
| 435 // |pending_transport_info_message_| exists. |
| 436 DCHECK_EQ(pending_transport_info_message_ != nullptr, |
| 437 transport_info_timer_.IsRunning()); |
| 438 |
| 439 if (!pending_transport_info_message_) { |
| 440 pending_transport_info_message_.reset( |
| 441 new XmlElement(QName(kTransportNamespace, "transport"), true)); |
| 442 |
| 443 // Delay sending the new candidates in case we get more candidates |
| 444 // that we can send in one message. |
| 445 transport_info_timer_.Start( |
| 446 FROM_HERE, base::TimeDelta::FromMilliseconds(kTransportInfoSendDelayMs), |
| 447 this, &WebrtcTransport::SendTransportInfo); |
| 448 } |
| 449 } |
| 450 |
| 451 void WebrtcTransport::SendTransportInfo() { |
| 452 DCHECK(thread_checker_.CalledOnValidThread()); |
| 453 DCHECK(pending_transport_info_message_); |
| 454 |
| 455 event_handler_->OnOutgoingTransportInfo( |
| 456 pending_transport_info_message_.Pass()); |
| 457 pending_transport_info_message_.reset(); |
| 458 } |
| 459 |
| 460 void WebrtcTransport::AddPendingCandidatesIfPossible() { |
| 461 DCHECK(thread_checker_.CalledOnValidThread()); |
| 462 |
| 463 if (peer_connection_->signaling_state() == |
| 464 webrtc::PeerConnectionInterface::kStable) { |
| 465 for (auto candidate : pending_incoming_candidates_) { |
| 466 if (!peer_connection_->AddIceCandidate(candidate)) { |
| 467 LOG(ERROR) << "Failed to add incoming candidate"; |
| 468 Close(INCOMPATIBLE_PROTOCOL); |
| 469 return; |
| 470 } |
| 471 } |
| 472 pending_incoming_candidates_.clear(); |
| 473 } |
| 474 } |
| 475 |
| 476 WebrtcTransportFactory::WebrtcTransportFactory( |
| 477 SignalStrategy* signal_strategy, |
| 478 rtc::scoped_refptr<webrtc::PortAllocatorFactoryInterface> |
| 479 port_allocator_factory, |
| 480 TransportRole role) |
| 481 : signal_strategy_(signal_strategy), |
| 482 port_allocator_factory_(port_allocator_factory), |
| 483 role_(role), |
| 484 worker_thread_("ChromotingWebrtcWorkerThread") { |
| 485 worker_thread_.StartWithOptions( |
| 486 base::Thread::Options(base::MessageLoop::TYPE_IO, 0)); |
| 487 } |
| 488 |
| 489 WebrtcTransportFactory::~WebrtcTransportFactory() {} |
| 490 |
| 491 scoped_ptr<Transport> WebrtcTransportFactory::CreateTransport() { |
| 492 return make_scoped_ptr(new WebrtcTransport(port_allocator_factory_, role_, |
| 493 worker_thread_.task_runner())); |
| 494 } |
| 495 |
| 496 } // namespace protocol |
| 497 } // namespace remoting |
OLD | NEW |