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 |