| 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
|
|
|