Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1517)

Unified Diff: remoting/protocol/webrtc_transport.cc

Issue 1427003009: Implement WebrtcTransport (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@transport_session.h
Patch Set: Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: remoting/protocol/webrtc_transport.cc
diff --git a/remoting/protocol/webrtc_transport.cc b/remoting/protocol/webrtc_transport.cc
new file mode 100644
index 0000000000000000000000000000000000000000..4bc629bb0f829d15482e8147cf957acc91fe03f4
--- /dev/null
+++ b/remoting/protocol/webrtc_transport.cc
@@ -0,0 +1,497 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/protocol/webrtc_transport.h"
+
+#include "base/callback_helpers.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/task_runner_util.h"
+#include "jingle/glue/thread_wrapper.h"
+#include "third_party/libjingle/source/talk/app/webrtc/test/fakeconstraints.h"
+#include "third_party/webrtc/libjingle/xmllite/xmlelement.h"
+#include "third_party/webrtc/modules/audio_device/include/fake_audio_device.h"
+
+using buzz::QName;
+using buzz::XmlElement;
+
+namespace remoting {
+namespace protocol {
+
+namespace {
+
+// Delay after candidate creation before sending transport-info message to
+// accumulate multiple candidates. This is an optimization to reduce number of
+// transport-info messages.
+const int kTransportInfoSendDelayMs = 20;
+
+// XML namespace for the transport elements.
+const char kTransportNamespace[] = "google:remoting:webrtc";
+
+rtc::Thread* InitAndGetRtcThread() {
+ jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
+
+ // TODO(sergeyu): Investigate if it's possible to avoid Send().
+ jingle_glue::JingleThreadWrapper::current()->set_send_allowed(true);
+
+ return jingle_glue::JingleThreadWrapper::current();
+}
+
+// A webrtc::CreateSessionDescriptionObserver implementation used to receive the
+// results of creating descriptions for this end of the PeerConnection.
+class CreateSessionDescriptionObserver
+ : public webrtc::CreateSessionDescriptionObserver {
+ public:
+ typedef base::Callback<void(
+ scoped_ptr<webrtc::SessionDescriptionInterface> description,
+ const std::string& error)> ResultCallback;
+
+ static CreateSessionDescriptionObserver* Create(
+ const ResultCallback& result_callback) {
+ return new rtc::RefCountedObject<CreateSessionDescriptionObserver>(
+ result_callback);
+ }
+ void OnSuccess(webrtc::SessionDescriptionInterface* desc) override {
+ base::ResetAndReturn(&result_callback_)
+ .Run(make_scoped_ptr(desc), std::string());
+ }
+ void OnFailure(const std::string& error) override {
+ base::ResetAndReturn(&result_callback_).Run(nullptr, error);
+ }
+
+ protected:
+ explicit CreateSessionDescriptionObserver(
+ const ResultCallback& result_callback)
+ : result_callback_(result_callback) {}
+ ~CreateSessionDescriptionObserver() override {}
+
+ private:
+ ResultCallback result_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(CreateSessionDescriptionObserver);
+};
+
+// A webrtc::SetSessionDescriptionObserver implementation used to receive the
+// results of setting local and remote descriptions of the PeerConnection.
+class SetSessionDescriptionObserver
+ : public webrtc::SetSessionDescriptionObserver {
+ public:
+ typedef base::Callback<void(bool success, const std::string& error)>
+ ResultCallback;
+
+ static SetSessionDescriptionObserver* Create(
+ const ResultCallback& result_callback) {
+ return new rtc::RefCountedObject<SetSessionDescriptionObserver>(
+ result_callback);
+ }
+
+ void OnSuccess() override {
+ base::ResetAndReturn(&result_callback_).Run(true, std::string());
+ }
+
+ void OnFailure(const std::string& error) override {
+ base::ResetAndReturn(&result_callback_).Run(false, error);
+ }
+
+ protected:
+ SetSessionDescriptionObserver(const ResultCallback& result_callback)
+ : result_callback_(result_callback) {}
+ ~SetSessionDescriptionObserver() override {}
+
+ private:
+ ResultCallback result_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(SetSessionDescriptionObserver);
+};
+
+} // namespace
+
+WebrtcTransport::WebrtcTransport(
+ rtc::scoped_refptr<webrtc::PortAllocatorFactoryInterface>
+ port_allocator_factory,
+ TransportRole role,
+ scoped_refptr<base::SingleThreadTaskRunner> worker_task_runner)
+ : port_allocator_factory_(port_allocator_factory),
+ role_(role),
+ worker_task_runner_(worker_task_runner),
+ weak_factory_(this) {}
+
+WebrtcTransport::~WebrtcTransport() {}
+
+void WebrtcTransport::Start(EventHandler* event_handler,
+ Authenticator* authenticator) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ event_handler_ = event_handler;
+
+ // TODO(sergeyu): Use the |authenticator| to authenticate PeerConnection.
+
+ base::PostTaskAndReplyWithResult(
+ worker_task_runner_.get(), FROM_HERE, base::Bind(&InitAndGetRtcThread),
+ base::Bind(&WebrtcTransport::DoStart, weak_factory_.GetWeakPtr()));
+}
+
+bool WebrtcTransport::ProcessTransportInfo(XmlElement* transport_info) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (transport_info->Name() != QName(kTransportNamespace, "transport"))
+ return false;
+
+ if (!peer_connection_)
+ return false;
+
+ XmlElement* session_description = transport_info->FirstNamed(
+ QName(kTransportNamespace, "session-description"));
+ if (session_description) {
+ webrtc::PeerConnectionInterface::SignalingState expected_state =
+ role_ == TransportRole::SERVER
+ ? webrtc::PeerConnectionInterface::kStable
+ : webrtc::PeerConnectionInterface::kHaveLocalOffer;
+ if (peer_connection_->signaling_state() != expected_state) {
+ LOG(ERROR) << "Received unexpected WebRTC session_description. ";
+ return false;
+ }
+
+ std::string type = session_description->Attr(QName(std::string(), "type"));
+ std::string sdp = session_description->BodyText();
+ if (type.empty() || sdp.empty()) {
+ LOG(ERROR) << "Incorrect session_description format.";
+ return false;
+ }
+
+ webrtc::SdpParseError error;
+ scoped_ptr<webrtc::SessionDescriptionInterface> session_description(
+ webrtc::CreateSessionDescription(type, sdp, &error));
+ if (!session_description) {
+ LOG(ERROR) << "Failed to parse the offer: " << error.description
+ << " line: " << error.line;
+ return false;
+ }
+
+ peer_connection_->SetRemoteDescription(
+ SetSessionDescriptionObserver::Create(
+ base::Bind(&WebrtcTransport::OnRemoteDescriptionSet,
+ weak_factory_.GetWeakPtr())),
+ session_description.release());
+ }
+
+ XmlElement* candidate_element;
+ QName candidate_qname(kTransportNamespace, "candidate");
+ for (candidate_element = transport_info->FirstNamed(candidate_qname);
+ candidate_element;
+ candidate_element = candidate_element->NextNamed(candidate_qname)) {
+ std::string candidate_str = candidate_element->BodyText();
+ std::string sdp_mid =
+ candidate_element->Attr(QName(std::string(), "sdpMid"));
+ std::string sdp_mlineindex_str =
+ candidate_element->Attr(QName(std::string(), "sdpMLineIndex"));
+ int sdp_mlineindex;
+ if (candidate_str.empty() || sdp_mid.empty() ||
+ !base::StringToInt(sdp_mlineindex_str, &sdp_mlineindex)) {
+ LOG(ERROR) << "Failed to parse incoming candidates.";
+ return false;
+ }
+
+ webrtc::SdpParseError error;
+ scoped_ptr<webrtc::IceCandidateInterface> candidate(
+ webrtc::CreateIceCandidate(sdp_mid, sdp_mlineindex, candidate_str,
+ &error));
+ if (!candidate) {
+ LOG(ERROR) << "Failed to parse incoming candidate: " << error.description
+ << " line: " << error.line;
+ return false;
+ }
+
+ if (peer_connection_->signaling_state() ==
+ webrtc::PeerConnectionInterface::kStable) {
+ if (!peer_connection_->AddIceCandidate(candidate.get())) {
+ LOG(ERROR) << "Failed to add incoming ICE candidate.";
+ return false;
+ }
+ } else {
+ pending_incoming_candidates_.push_back(candidate.Pass());
+ }
+ }
+
+ return true;
+}
+
+DatagramChannelFactory* WebrtcTransport::GetDatagramChannelFactory() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ NOTIMPLEMENTED();
+ return nullptr;
+}
+
+StreamChannelFactory* WebrtcTransport::GetStreamChannelFactory() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ // TODO(sergeyu): Implement data stream support.
+ NOTIMPLEMENTED();
+ return nullptr;
+}
+
+StreamChannelFactory* WebrtcTransport::GetMultiplexedChannelFactory() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return GetStreamChannelFactory();
+}
+
+void WebrtcTransport::DoStart(rtc::Thread* worker_thread) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
+
+ // TODO(sergeyu): Investigate if it's possible to avoid Send().
+ jingle_glue::JingleThreadWrapper::current()->set_send_allowed(true);
+
+ fake_audio_device_module_.reset(new webrtc::FakeAudioDeviceModule());
+
+ peer_connection_factory_ = webrtc::CreatePeerConnectionFactory(
+ worker_thread, rtc::Thread::Current(),
+ fake_audio_device_module_.get(), nullptr, nullptr);
+
+ webrtc::PeerConnectionInterface::IceServer stun_server;
+ stun_server.urls.push_back("stun:stun.l.google.com:19302");
+ webrtc::PeerConnectionInterface::RTCConfiguration rtc_config;
+ rtc_config.servers.push_back(stun_server);
+
+ webrtc::FakeConstraints constraints;
+ constraints.AddMandatory(webrtc::MediaConstraintsInterface::kEnableDtlsSrtp,
+ webrtc::MediaConstraintsInterface::kValueTrue);
+
+ peer_connection_ = peer_connection_factory_->CreatePeerConnection(
+ rtc_config, &constraints, port_allocator_factory_, nullptr, this);
+
+ if (role_ == TransportRole::CLIENT) {
+ webrtc::FakeConstraints offer_config;
+ offer_config.AddMandatory(
+ webrtc::MediaConstraintsInterface::kOfferToReceiveVideo,
+ webrtc::MediaConstraintsInterface::kValueTrue);
+ offer_config.AddMandatory(
+ webrtc::MediaConstraintsInterface::kOfferToReceiveAudio,
+ webrtc::MediaConstraintsInterface::kValueFalse);
+ peer_connection_->CreateOffer(
+ CreateSessionDescriptionObserver::Create(
+ base::Bind(&WebrtcTransport::OnLocalSessionDescriptionCreated,
+ weak_factory_.GetWeakPtr())),
+ &offer_config);
+ }
+}
+
+void WebrtcTransport::OnLocalSessionDescriptionCreated(
+ scoped_ptr<webrtc::SessionDescriptionInterface> description,
+ const std::string& error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (!peer_connection_)
+ return;
+
+ if (!description) {
+ LOG(ERROR) << "PeerConnection offer creation failed: " << error;
+ Close(CHANNEL_CONNECTION_ERROR);
+ return;
+ }
+
+ std::string description_sdp;
+ if (!description->ToString(&description_sdp)) {
+ LOG(ERROR) << "Failed to serialize description.";
+ Close(CHANNEL_CONNECTION_ERROR);
+ return;
+ }
+
+ // Format and send the session description to the peer.
+ scoped_ptr<XmlElement> transport_info(
+ new XmlElement(QName(kTransportNamespace, "transport"), true));
+ XmlElement* offer_tag =
+ new XmlElement(QName(kTransportNamespace, "session-description"));
+ transport_info->AddElement(offer_tag);
+ offer_tag->SetAttr(QName(std::string(), "type"), description->type());
+ offer_tag->SetBodyText(description_sdp);
+
+ event_handler_->OnOutgoingTransportInfo(transport_info.Pass());
+
+ peer_connection_->SetLocalDescription(
+ SetSessionDescriptionObserver::Create(base::Bind(
+ &WebrtcTransport::OnLocalDescriptionSet, weak_factory_.GetWeakPtr())),
+ description.release());
+}
+
+void WebrtcTransport::OnLocalDescriptionSet(bool success,
+ const std::string& error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (!peer_connection_)
+ return;
+
+ if (!success) {
+ LOG(ERROR) << "Failed to set local description: " << error;
+ Close(CHANNEL_CONNECTION_ERROR);
+ return;
+ }
+
+ AddPendingCandidatesIfPossible();
+}
+
+void WebrtcTransport::OnRemoteDescriptionSet(bool success,
+ const std::string& error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (!peer_connection_)
+ return;
+
+ if (!success) {
+ LOG(ERROR) << "Failed to set local description: " << error;
+ Close(CHANNEL_CONNECTION_ERROR);
+ return;
+ }
+
+ // Create and send answer on the server.
+ if (role_ == TransportRole::SERVER) {
+ peer_connection_->CreateAnswer(
+ CreateSessionDescriptionObserver::Create(
+ base::Bind(&WebrtcTransport::OnLocalSessionDescriptionCreated,
+ weak_factory_.GetWeakPtr())),
+ nullptr);
+ }
+
+ AddPendingCandidatesIfPossible();
+}
+
+void WebrtcTransport::Close(ErrorCode error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ weak_factory_.InvalidateWeakPtrs();
+ peer_connection_->Close();
+ peer_connection_ = nullptr;
+ peer_connection_factory_ = nullptr;
+
+ if (error != OK)
+ event_handler_->OnTransportError(error);
+}
+
+void WebrtcTransport::OnSignalingChange(
+ webrtc::PeerConnectionInterface::SignalingState new_state) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void WebrtcTransport::OnAddStream(webrtc::MediaStreamInterface* stream) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ LOG(ERROR) << "Stream added " << stream->label();
+}
+
+void WebrtcTransport::OnRemoveStream(webrtc::MediaStreamInterface* stream) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ LOG(ERROR) << "Stream removed " << stream->label();
+}
+
+void WebrtcTransport::OnDataChannel(
+ webrtc::DataChannelInterface* data_channel) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ // TODO(sergeyu): Use the data channel.
+}
+
+void WebrtcTransport::OnRenegotiationNeeded() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ // TODO(sergeyu): Figure out what needs to happen here.
+}
+
+void WebrtcTransport::OnIceConnectionChange(
+ webrtc::PeerConnectionInterface::IceConnectionState new_state) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (new_state == webrtc::PeerConnectionInterface::kIceConnectionConnected)
+ event_handler_->OnTransportConnected();
+}
+
+void WebrtcTransport::OnIceGatheringChange(
+ webrtc::PeerConnectionInterface::IceGatheringState new_state) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void WebrtcTransport::OnIceCandidate(
+ const webrtc::IceCandidateInterface* candidate) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ scoped_ptr<XmlElement> candidate_element(
+ new XmlElement(QName(kTransportNamespace, "candidate")));
+ std::string candidate_str;
+ if (!candidate->ToString(&candidate_str)) {
+ LOG(ERROR) << "Failed to serialize local candidate.";
+ return;
+ }
+ candidate_element->SetBodyText(candidate_str);
+ candidate_element->SetAttr(QName(std::string(), "sdpMid"),
+ candidate->sdp_mid());
+ candidate_element->SetAttr(QName(std::string(), "sdpMLineIndex"),
+ base::IntToString(candidate->sdp_mline_index()));
+
+ EnsurePendingTransportInfoMessage();
+ pending_transport_info_message_->AddElement(candidate_element.release());
+}
+
+void WebrtcTransport::EnsurePendingTransportInfoMessage() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // |transport_info_timer_| must be running iff
+ // |pending_transport_info_message_| exists.
+ DCHECK_EQ(pending_transport_info_message_ != nullptr,
+ transport_info_timer_.IsRunning());
+
+ if (!pending_transport_info_message_) {
+ pending_transport_info_message_.reset(
+ new XmlElement(QName(kTransportNamespace, "transport"), true));
+
+ // Delay sending the new candidates in case we get more candidates
+ // that we can send in one message.
+ transport_info_timer_.Start(
+ FROM_HERE, base::TimeDelta::FromMilliseconds(kTransportInfoSendDelayMs),
+ this, &WebrtcTransport::SendTransportInfo);
+ }
+}
+
+void WebrtcTransport::SendTransportInfo() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(pending_transport_info_message_);
+
+ event_handler_->OnOutgoingTransportInfo(
+ pending_transport_info_message_.Pass());
+ pending_transport_info_message_.reset();
+}
+
+void WebrtcTransport::AddPendingCandidatesIfPossible() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (peer_connection_->signaling_state() ==
+ webrtc::PeerConnectionInterface::kStable) {
+ for (auto candidate : pending_incoming_candidates_) {
+ if (!peer_connection_->AddIceCandidate(candidate)) {
+ LOG(ERROR) << "Failed to add incoming candidate";
+ Close(INCOMPATIBLE_PROTOCOL);
+ return;
+ }
+ }
+ pending_incoming_candidates_.clear();
+ }
+}
+
+WebrtcTransportFactory::WebrtcTransportFactory(
+ SignalStrategy* signal_strategy,
+ rtc::scoped_refptr<webrtc::PortAllocatorFactoryInterface>
+ port_allocator_factory,
+ TransportRole role)
+ : signal_strategy_(signal_strategy),
+ port_allocator_factory_(port_allocator_factory),
+ role_(role),
+ worker_thread_("ChromotingWebrtcWorkerThread") {
+ worker_thread_.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
+}
+
+WebrtcTransportFactory::~WebrtcTransportFactory() {}
+
+scoped_ptr<Transport> WebrtcTransportFactory::CreateTransport() {
+ return make_scoped_ptr(new WebrtcTransport(port_allocator_factory_, role_,
+ worker_thread_.task_runner()));
+}
+
+} // namespace protocol
+} // namespace remoting

Powered by Google App Engine
This is Rietveld 408576698