Chromium Code Reviews| 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 event_handler_ = event_handler; | |
| 125 // FIXME: use authenticator. | |
|
kelvinp
2015/11/11 23:38:48
Should this be a TODO statement so that it is cons
Sergey Ulanov
2015/11/17 01:40:32
Done.
| |
| 126 | |
| 127 base::PostTaskAndReplyWithResult( | |
| 128 worker_task_runner_.get(), FROM_HERE, base::Bind(&InitAndGetRtcThread), | |
| 129 base::Bind(&WebrtcTransport::DoStart, weak_factory_.GetWeakPtr())); | |
| 130 } | |
| 131 | |
| 132 bool WebrtcTransport::ProcessTransportInfo(XmlElement* transport_info) { | |
| 133 if (transport_info->Name() != QName(kTransportNamespace, "transport")) | |
| 134 return false; | |
| 135 | |
| 136 if (!peer_connection_) | |
| 137 return false; | |
| 138 | |
| 139 XmlElement* session_description = transport_info->FirstNamed( | |
| 140 QName(kTransportNamespace, "session-description")); | |
| 141 if (session_description) { | |
| 142 webrtc::PeerConnectionInterface::SignalingState expected_state = | |
| 143 role_ == TransportRole::SERVER | |
| 144 ? webrtc::PeerConnectionInterface::kStable | |
| 145 : webrtc::PeerConnectionInterface::kHaveLocalOffer; | |
| 146 if (peer_connection_->signaling_state() != expected_state) { | |
| 147 LOG(ERROR) << "Received unexpected WebRTC session_description. "; | |
| 148 return false; | |
| 149 } | |
| 150 | |
| 151 std::string type = session_description->Attr(QName(std::string(), "type")); | |
| 152 std::string sdp = session_description->BodyText(); | |
| 153 if (type.empty() || sdp.empty()) { | |
| 154 LOG(ERROR) << "Incorrect session_description format."; | |
| 155 return false; | |
| 156 } | |
| 157 | |
| 158 webrtc::SdpParseError error; | |
| 159 scoped_ptr<webrtc::SessionDescriptionInterface> session_description( | |
| 160 webrtc::CreateSessionDescription(type, sdp, &error)); | |
| 161 if (!session_description) { | |
| 162 LOG(ERROR) << "Failed to parse the offer: " << error.description | |
| 163 << " line: " << error.line; | |
| 164 return false; | |
| 165 } | |
| 166 | |
| 167 peer_connection_->SetRemoteDescription( | |
| 168 SetSessionDescriptionObserver::Create( | |
| 169 base::Bind(&WebrtcTransport::OnRemoteDescriptionSet, | |
| 170 weak_factory_.GetWeakPtr())), | |
| 171 session_description.release()); | |
| 172 } | |
| 173 | |
| 174 XmlElement* candidate_element; | |
| 175 QName candidate_qname(kTransportNamespace, "candidate"); | |
| 176 for (candidate_element = transport_info->FirstNamed(candidate_qname); | |
| 177 candidate_element; | |
| 178 candidate_element = candidate_element->NextNamed(candidate_qname)) { | |
| 179 std::string candidate_str = candidate_element->BodyText(); | |
| 180 std::string sdp_mid = | |
| 181 candidate_element->Attr(QName(std::string(), "sdpMid")); | |
| 182 std::string sdp_mlineindex_str = | |
| 183 candidate_element->Attr(QName(std::string(), "sdpMLineIndex")); | |
| 184 int sdp_mlineindex; | |
| 185 if (candidate_str.empty() || sdp_mid.empty() || | |
| 186 !base::StringToInt(sdp_mlineindex_str, &sdp_mlineindex)) { | |
| 187 LOG(ERROR) << "Failed to parse incoming candidates."; | |
| 188 return false; | |
| 189 } | |
| 190 | |
| 191 webrtc::SdpParseError error; | |
| 192 scoped_ptr<webrtc::IceCandidateInterface> candidate( | |
| 193 webrtc::CreateIceCandidate(sdp_mid, sdp_mlineindex, candidate_str, | |
| 194 &error)); | |
| 195 if (!candidate) { | |
| 196 LOG(ERROR) << "Failed to parse incoming candidate: " << error.description | |
| 197 << " line: " << error.line; | |
| 198 return false; | |
| 199 } | |
| 200 | |
| 201 if (peer_connection_->signaling_state() == | |
| 202 webrtc::PeerConnectionInterface::kStable) { | |
| 203 if (!peer_connection_->AddIceCandidate(candidate.get())) { | |
| 204 LOG(ERROR) << "Failed to add incoming ICE candidate."; | |
| 205 return false; | |
| 206 } | |
| 207 } else { | |
| 208 pending_incoming_candidates_.push_back(candidate.Pass()); | |
| 209 } | |
| 210 } | |
| 211 | |
| 212 return true; | |
| 213 } | |
| 214 | |
| 215 DatagramChannelFactory* WebrtcTransport::GetDatagramChannelFactory() { | |
| 216 NOTIMPLEMENTED(); | |
| 217 return nullptr; | |
| 218 } | |
| 219 | |
| 220 StreamChannelFactory* WebrtcTransport::GetStreamChannelFactory() { | |
| 221 // TODO(sergeyu): Implement data stream support. | |
| 222 NOTIMPLEMENTED(); | |
| 223 return nullptr; | |
| 224 } | |
| 225 | |
| 226 StreamChannelFactory* WebrtcTransport::GetMultiplexedChannelFactory() { | |
| 227 return GetStreamChannelFactory(); | |
| 228 } | |
| 229 | |
| 230 void WebrtcTransport::DoStart(rtc::Thread* worker_thread) { | |
| 231 jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop(); | |
| 232 | |
| 233 // TODO(sergeyu): Investigate if it's possible to avoid Send(). | |
| 234 jingle_glue::JingleThreadWrapper::current()->set_send_allowed(true); | |
| 235 | |
| 236 peer_connection_factory_ = webrtc::CreatePeerConnectionFactory( | |
| 237 worker_thread, rtc::Thread::Current(), | |
| 238 new webrtc::FakeAudioDeviceModule(), nullptr, nullptr); | |
| 239 | |
| 240 webrtc::PeerConnectionInterface::IceServer stun_server; | |
| 241 stun_server.urls.push_back("stun:stun.l.google.com:19302"); | |
| 242 webrtc::PeerConnectionInterface::RTCConfiguration rtc_config; | |
| 243 rtc_config.servers.push_back(stun_server); | |
| 244 | |
| 245 webrtc::FakeConstraints constraints; | |
| 246 constraints.AddMandatory(webrtc::MediaConstraintsInterface::kEnableDtlsSrtp, | |
| 247 webrtc::MediaConstraintsInterface::kValueTrue); | |
| 248 | |
| 249 peer_connection_ = peer_connection_factory_->CreatePeerConnection( | |
| 250 rtc_config, &constraints, port_allocator_factory_, nullptr, this); | |
| 251 | |
| 252 if (role_ == TransportRole::CLIENT) { | |
| 253 webrtc::FakeConstraints offer_config; | |
| 254 offer_config.AddMandatory( | |
| 255 webrtc::MediaConstraintsInterface::kOfferToReceiveVideo, | |
| 256 webrtc::MediaConstraintsInterface::kValueTrue); | |
| 257 offer_config.AddMandatory( | |
| 258 webrtc::MediaConstraintsInterface::kOfferToReceiveAudio, | |
| 259 webrtc::MediaConstraintsInterface::kValueFalse); | |
| 260 peer_connection_->CreateOffer( | |
| 261 CreateSessionDescriptionObserver::Create( | |
| 262 base::Bind(&WebrtcTransport::OnLocalSessionDescriptionCreated, | |
| 263 weak_factory_.GetWeakPtr())), | |
| 264 &offer_config); | |
| 265 } | |
| 266 } | |
| 267 | |
| 268 void WebrtcTransport::OnLocalSessionDescriptionCreated( | |
| 269 scoped_ptr<webrtc::SessionDescriptionInterface> description, | |
| 270 const std::string& error) { | |
| 271 if (!peer_connection_) | |
| 272 return; | |
| 273 | |
| 274 if (!description) { | |
| 275 LOG(ERROR) << "PeerConnection offer creation failed: " << error; | |
| 276 Close(CHANNEL_CONNECTION_ERROR); | |
| 277 return; | |
| 278 } | |
| 279 | |
| 280 std::string description_sdp; | |
| 281 if (!description->ToString(&description_sdp)) { | |
| 282 LOG(ERROR) << "Failed to serialize description."; | |
| 283 Close(CHANNEL_CONNECTION_ERROR); | |
| 284 return; | |
| 285 } | |
| 286 | |
| 287 // Format and send the session description to the peer. | |
| 288 scoped_ptr<XmlElement> transport_info( | |
| 289 new XmlElement(QName(kTransportNamespace, "transport"), true)); | |
| 290 XmlElement* offer_tag = | |
| 291 new XmlElement(QName(kTransportNamespace, "session-description")); | |
| 292 transport_info->AddElement(offer_tag); | |
| 293 offer_tag->SetAttr(QName(std::string(), "type"), description->type()); | |
| 294 offer_tag->SetBodyText(description_sdp); | |
| 295 | |
| 296 event_handler_->OnOutgoingTransportInfo(transport_info.Pass()); | |
|
kelvinp
2015/11/11 23:38:48
Do we need to post back to the caller thread?
Sergey Ulanov
2015/11/17 01:40:32
No. All code in this class runs on the network thr
| |
| 297 | |
| 298 peer_connection_->SetLocalDescription( | |
| 299 SetSessionDescriptionObserver::Create(base::Bind( | |
| 300 &WebrtcTransport::OnLocalDescriptionSet, weak_factory_.GetWeakPtr())), | |
| 301 description.release()); | |
| 302 } | |
| 303 | |
| 304 void WebrtcTransport::OnLocalDescriptionSet(bool success, | |
| 305 const std::string& error) { | |
| 306 if (!peer_connection_) | |
| 307 return; | |
| 308 | |
| 309 if (!success) { | |
| 310 LOG(ERROR) << "Failed to set local description: " << error; | |
| 311 Close(CHANNEL_CONNECTION_ERROR); | |
| 312 return; | |
| 313 } | |
| 314 | |
| 315 AddPendingCandidatesIfPossible(); | |
| 316 } | |
| 317 | |
| 318 void WebrtcTransport::OnRemoteDescriptionSet(bool success, | |
| 319 const std::string& error) { | |
| 320 if (!peer_connection_) | |
| 321 return; | |
| 322 | |
| 323 if (!success) { | |
| 324 LOG(ERROR) << "Failed to set local description: " << error; | |
| 325 Close(CHANNEL_CONNECTION_ERROR); | |
| 326 return; | |
| 327 } | |
| 328 | |
| 329 // Create and send answer on the server. | |
| 330 if (role_ == TransportRole::SERVER) { | |
| 331 peer_connection_->CreateAnswer( | |
| 332 CreateSessionDescriptionObserver::Create( | |
| 333 base::Bind(&WebrtcTransport::OnLocalSessionDescriptionCreated, | |
| 334 weak_factory_.GetWeakPtr())), | |
| 335 nullptr); | |
| 336 } | |
| 337 | |
| 338 AddPendingCandidatesIfPossible(); | |
| 339 } | |
| 340 | |
| 341 void WebrtcTransport::Close(ErrorCode error) { | |
| 342 weak_factory_.InvalidateWeakPtrs(); | |
| 343 peer_connection_->Close(); | |
| 344 peer_connection_ = nullptr; | |
| 345 peer_connection_factory_ = nullptr; | |
| 346 | |
| 347 if (error != OK) | |
| 348 event_handler_->OnTransportError(error); | |
| 349 } | |
| 350 | |
| 351 void WebrtcTransport::OnSignalingChange( | |
| 352 webrtc::PeerConnectionInterface::SignalingState new_state) { | |
| 353 } | |
| 354 | |
| 355 void WebrtcTransport::OnAddStream(webrtc::MediaStreamInterface* stream) { | |
| 356 unclaimed_streams_.push_back(stream); | |
| 357 LOG(ERROR) << "Stream added " << stream->label(); | |
| 358 } | |
| 359 | |
| 360 void WebrtcTransport::OnRemoveStream(webrtc::MediaStreamInterface* stream) { | |
| 361 LOG(ERROR) << "Stream removed " << stream->label(); | |
| 362 } | |
| 363 | |
| 364 void WebrtcTransport::OnDataChannel( | |
| 365 webrtc::DataChannelInterface* data_channel) {} | |
| 366 | |
| 367 void WebrtcTransport::OnRenegotiationNeeded() { | |
| 368 // TODO(sergeyu): Figure out what needs to happen here. | |
| 369 } | |
| 370 | |
| 371 void WebrtcTransport::OnIceConnectionChange( | |
| 372 webrtc::PeerConnectionInterface::IceConnectionState new_state) { | |
| 373 if (new_state == webrtc::PeerConnectionInterface::kIceConnectionConnected) | |
| 374 event_handler_->OnTransportConnected(); | |
| 375 } | |
| 376 | |
| 377 void WebrtcTransport::OnIceGatheringChange( | |
| 378 webrtc::PeerConnectionInterface::IceGatheringState new_state) { | |
| 379 } | |
| 380 | |
| 381 void WebrtcTransport::OnIceCandidate( | |
| 382 const webrtc::IceCandidateInterface* candidate) { | |
| 383 scoped_ptr<XmlElement> candidate_element( | |
| 384 new XmlElement(QName(kTransportNamespace, "candidate"))); | |
| 385 std::string candidate_str; | |
| 386 if (!candidate->ToString(&candidate_str)) { | |
| 387 LOG(ERROR) << "Failed to serialize local candidate."; | |
| 388 return; | |
| 389 } | |
| 390 candidate_element->SetBodyText(candidate_str); | |
| 391 candidate_element->SetAttr(QName(std::string(), "sdpMid"), | |
| 392 candidate->sdp_mid()); | |
| 393 candidate_element->SetAttr(QName(std::string(), "sdpMLineIndex"), | |
| 394 base::IntToString(candidate->sdp_mline_index())); | |
| 395 | |
| 396 EnsurePendingTransportInfoMessage(); | |
| 397 pending_transport_info_message_->AddElement(candidate_element.release()); | |
| 398 } | |
| 399 | |
| 400 void WebrtcTransport::EnsurePendingTransportInfoMessage() { | |
| 401 // |transport_info_timer_| must be running iff | |
| 402 // |pending_transport_info_message_| exists. | |
| 403 DCHECK_EQ(pending_transport_info_message_ != nullptr, | |
| 404 transport_info_timer_.IsRunning()); | |
| 405 | |
| 406 if (!pending_transport_info_message_) { | |
| 407 pending_transport_info_message_.reset( | |
| 408 new XmlElement(QName(kTransportNamespace, "transport"), true)); | |
| 409 | |
| 410 // Delay sending the new candidates in case we get more candidates | |
| 411 // that we can send in one message. | |
| 412 transport_info_timer_.Start( | |
| 413 FROM_HERE, base::TimeDelta::FromMilliseconds(kTransportInfoSendDelayMs), | |
| 414 this, &WebrtcTransport::SendTransportInfo); | |
| 415 } | |
| 416 } | |
| 417 | |
| 418 void WebrtcTransport::SendTransportInfo() { | |
| 419 DCHECK(pending_transport_info_message_); | |
| 420 event_handler_->OnOutgoingTransportInfo( | |
| 421 pending_transport_info_message_.Pass()); | |
| 422 pending_transport_info_message_.reset(); | |
| 423 } | |
| 424 | |
| 425 void WebrtcTransport::AddPendingCandidatesIfPossible() { | |
| 426 if (peer_connection_->signaling_state() == | |
| 427 webrtc::PeerConnectionInterface::kStable) { | |
| 428 for (auto candidate : pending_incoming_candidates_) { | |
| 429 if (!peer_connection_->AddIceCandidate(candidate)) { | |
| 430 LOG(ERROR) << "Failed to add incoming candidate"; | |
| 431 Close(INCOMPATIBLE_PROTOCOL); | |
| 432 return; | |
| 433 } | |
| 434 } | |
| 435 pending_incoming_candidates_.clear(); | |
| 436 } | |
| 437 } | |
| 438 | |
| 439 WebrtcTransportFactory::WebrtcTransportFactory( | |
| 440 SignalStrategy* signal_strategy, | |
| 441 rtc::scoped_refptr<webrtc::PortAllocatorFactoryInterface> | |
| 442 port_allocator_factory, | |
| 443 TransportRole role) | |
| 444 : signal_strategy_(signal_strategy), | |
| 445 port_allocator_factory_(port_allocator_factory), | |
| 446 role_(role), | |
| 447 worker_thread_("ChromotingWebrtcWorkerThread") { | |
| 448 worker_thread_.StartWithOptions( | |
| 449 base::Thread::Options(base::MessageLoop::TYPE_IO, 0)); | |
| 450 } | |
| 451 | |
| 452 WebrtcTransportFactory::~WebrtcTransportFactory() {} | |
| 453 | |
| 454 scoped_ptr<Transport> WebrtcTransportFactory::CreateTransport() { | |
| 455 return make_scoped_ptr(new WebrtcTransport(port_allocator_factory_, role_, | |
| 456 worker_thread_.task_runner())); | |
| 457 } | |
| 458 | |
| 459 } // namespace protocol | |
| 460 } // namespace remoting | |
| OLD | NEW |