1 // Copyright 2014 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.
5 #include "base/base64.h"
6 #include "base/file_util.h"
7 #include "base/message_loop/message_loop.h"
8 #include "base/run_loop.h"
9 #include "base/single_thread_task_runner.h"
10 #include "base/synchronization/waitable_event.h"
11 #include "base/thread_task_runner_handle.h"
12 #include "jingle/glue/thread_wrapper.h"
13 #include "net/base/test_data_directory.h"
14 #include "net/url_request/url_request_context_getter.h"
15 #include "remoting/base/rsa_key_pair.h"
16 #include "remoting/client/audio_player.h"
17 #include "remoting/client/chromoting_client.h"
18 #include "remoting/client/client_context.h"
19 #include "remoting/client/client_user_interface.h"
20 #include "remoting/client/video_renderer.h"
21 #include "remoting/host/chromoting_host.h"
22 #include "remoting/host/chromoting_host_context.h"
23 #include "remoting/host/fake_desktop_environment.h"
24 #include "remoting/host/video_scheduler.h"
25 #include "remoting/protocol/chromium_port_allocator.h"
26 #include "remoting/protocol/jingle_session_manager.h"
27 #include "remoting/protocol/libjingle_transport_factory.h"
28 #include "remoting/protocol/me2me_host_authenticator_factory.h"
29 #include "remoting/protocol/negotiating_client_authenticator.h"
30 #include "remoting/protocol/session_config.h"
31 #include "remoting/signaling/fake_signal_strategy.h"
32 #include "testing/gtest/include/gtest/gtest.h"
36 using protocol::ChannelConfig
;
38 const char kHostJid
[] = "host_jid@example.com/host";
39 const char kHostOwner
[] = "jane.doe@example.com";
40 const char kClientJid
[] = "jane.doe@example.com/client";
42 class ProtocolPerfTest
: public testing::Test
,
43 public ClientUserInterface
,
45 public HostStatusObserver
{
48 : host_thread_("host"),
49 capture_thread_("capture"),
50 encode_thread_("encode") {
51 VideoScheduler::EnableTimestampsForTests();
52 host_thread_
.StartWithOptions(
53 base::Thread::Options(base::MessageLoop::TYPE_IO
, 0));
54 capture_thread_
.Start();
55 encode_thread_
.Start();
57 virtual ~ProtocolPerfTest() {
58 host_thread_
.message_loop_proxy()->DeleteSoon(FROM_HERE
, host_
.release());
59 host_thread_
.message_loop_proxy()->DeleteSoon(FROM_HERE
,
60 host_signaling_
.release());
61 message_loop_
.RunUntilIdle();
64 // ClientUserInterface interface.
65 virtual void OnConnectionState(protocol::ConnectionToHost::State state
,
66 protocol::ErrorCode error
) OVERRIDE
{
67 if (state
== protocol::ConnectionToHost::CONNECTED
) {
68 client_connected_
= true;
70 connecting_loop_
->Quit();
73 virtual void OnConnectionReady(bool ready
) OVERRIDE
{}
74 virtual void OnRouteChanged(const std::string
& channel_name
,
75 const protocol::TransportRoute
& route
) OVERRIDE
{
77 virtual void SetCapabilities(const std::string
& capabilities
) OVERRIDE
{}
78 virtual void SetPairingResponse(
79 const protocol::PairingResponse
& pairing_response
) OVERRIDE
{}
80 virtual void DeliverHostMessage(
81 const protocol::ExtensionMessage
& message
) OVERRIDE
{}
82 virtual protocol::ClipboardStub
* GetClipboardStub() OVERRIDE
{
85 virtual protocol::CursorShapeStub
* GetCursorShapeStub() OVERRIDE
{
89 // VideoRenderer interface.
90 virtual void Initialize(const protocol::SessionConfig
& config
) OVERRIDE
{}
91 virtual ChromotingStats
* GetStats() OVERRIDE
{ return NULL
; }
92 virtual void ProcessVideoPacket(scoped_ptr
<VideoPacket
> video_packet
,
93 const base::Closure
& done
) OVERRIDE
{
94 if (video_packet
->data().empty()) {
95 // Ignore keep-alive packets
100 last_video_packet_
= video_packet
.Pass();
102 if (!on_frame_task_
.is_null())
103 on_frame_task_
.Run();
108 // HostStatusObserver interface.
109 virtual void OnClientConnected(const std::string
& jid
) OVERRIDE
{
110 message_loop_
.PostTask(
112 base::Bind(&ProtocolPerfTest::OnHostConnectedMainThread
,
113 base::Unretained(this)));
117 void WaitConnected() {
118 client_connected_
= false;
119 host_connected_
= false;
121 connecting_loop_
.reset(new base::RunLoop());
122 connecting_loop_
->Run();
124 ASSERT_TRUE(client_connected_
&& host_connected_
);
127 void OnHostConnectedMainThread() {
128 host_connected_
= true;
129 if (client_connected_
)
130 connecting_loop_
->Quit();
133 void ReceiveFrame(base::TimeDelta
* latency
) {
134 waiting_frames_loop_
.reset(new base::RunLoop());
135 on_frame_task_
= waiting_frames_loop_
->QuitClosure();
136 waiting_frames_loop_
->Run();
139 base::TimeTicks timestamp
=
140 base::TimeTicks::FromInternalValue(last_video_packet_
->timestamp());
141 *latency
= base::TimeTicks::Now() - timestamp
;
145 void ReceiveFrames(int frames
, base::TimeDelta
* max_latency
) {
147 *max_latency
= base::TimeDelta();
149 for (int i
= 0; i
< frames
; ++i
) {
150 base::TimeDelta latency
;
152 ReceiveFrame(&latency
);
154 if (max_latency
&& latency
> *max_latency
) {
155 *max_latency
= latency
;
160 // Creates test host and client and starts connection between them. Caller
161 // should call WaitConnected() to wait until connection is established. The
162 // host is started on |host_thread_| while the client works on the main
164 void StartHostAndClient(protocol::ChannelConfig::Codec video_codec
) {
165 client_signaling_
.reset(new FakeSignalStrategy(kClientJid
));
167 jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
169 protocol_config_
= protocol::CandidateSessionConfig::CreateDefault();
170 protocol_config_
->DisableAudioChannel();
171 protocol_config_
->mutable_video_configs()->clear();
172 protocol_config_
->mutable_video_configs()->push_back(
173 protocol::ChannelConfig(
174 protocol::ChannelConfig::TRANSPORT_STREAM
, 2, video_codec
));
176 host_thread_
.message_loop_proxy()->PostTask(
178 base::Bind(&ProtocolPerfTest::StartHost
, base::Unretained(this)));
182 DCHECK(host_thread_
.message_loop_proxy()->BelongsToCurrentThread());
184 jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
186 host_signaling_
.reset(new FakeSignalStrategy(kHostJid
));
187 host_signaling_
->ConnectTo(client_signaling_
.get());
189 protocol::NetworkSettings
network_settings(
190 protocol::NetworkSettings::NAT_TRAVERSAL_OUTGOING
);
192 // TODO(sergeyu): Replace with a fake port allocator.
193 scoped_ptr
<cricket::HttpPortAllocatorBase
> host_port_allocator
=
194 protocol::ChromiumPortAllocator::Create(NULL
, network_settings
)
195 .PassAs
<cricket::HttpPortAllocatorBase
>();
197 scoped_ptr
<protocol::TransportFactory
> host_transport_factory(
198 new protocol::LibjingleTransportFactory(
199 host_signaling_
.get(),
200 host_port_allocator
.Pass(),
203 scoped_ptr
<protocol::SessionManager
> session_manager(
204 new protocol::JingleSessionManager(host_transport_factory
.Pass()));
206 // Encoder runs on a separate thread, main thread is used for everything
208 host_
.reset(new ChromotingHost(host_signaling_
.get(),
209 &desktop_environment_factory_
,
210 session_manager
.Pass(),
211 host_thread_
.message_loop_proxy(),
212 host_thread_
.message_loop_proxy(),
213 capture_thread_
.message_loop_proxy(),
214 encode_thread_
.message_loop_proxy(),
215 host_thread_
.message_loop_proxy(),
216 host_thread_
.message_loop_proxy()));
218 base::FilePath
certs_dir(net::GetTestCertsDirectory());
220 std::string host_cert
;
221 ASSERT_TRUE(base::ReadFileToString(
222 certs_dir
.AppendASCII("unittest.selfsigned.der"), &host_cert
));
224 base::FilePath key_path
= certs_dir
.AppendASCII("unittest.key.bin");
225 std::string key_string
;
226 ASSERT_TRUE(base::ReadFileToString(key_path
, &key_string
));
227 std::string key_base64
;
228 base::Base64Encode(key_string
, &key_base64
);
229 scoped_refptr
<RsaKeyPair
> key_pair
= RsaKeyPair::FromString(key_base64
);
230 ASSERT_TRUE(key_pair
.get());
233 protocol::SharedSecretHash host_secret
;
234 host_secret
.hash_function
= protocol::AuthenticationMethod::NONE
;
235 host_secret
.value
= "123456";
236 scoped_ptr
<protocol::AuthenticatorFactory
> auth_factory
=
237 protocol::Me2MeHostAuthenticatorFactory::CreateWithSharedSecret(
238 true, kHostOwner
, host_cert
, key_pair
, host_secret
, NULL
);
239 host_
->SetAuthenticatorFactory(auth_factory
.Pass());
241 host_
->AddStatusObserver(this);
242 host_
->set_protocol_config(protocol_config_
->Clone());
243 host_
->Start(kHostOwner
);
245 message_loop_
.PostTask(FROM_HERE
,
246 base::Bind(&ProtocolPerfTest::StartClientAfterHost
,
247 base::Unretained(this)));
250 void StartClientAfterHost() {
251 client_signaling_
->ConnectTo(host_signaling_
.get());
253 protocol::NetworkSettings
network_settings(
254 protocol::NetworkSettings::NAT_TRAVERSAL_OUTGOING
);
256 // Initialize client.
257 client_context_
.reset(
258 new ClientContext(base::ThreadTaskRunnerHandle::Get()));
260 // TODO(sergeyu): Replace with a fake port allocator
261 scoped_ptr
<cricket::HttpPortAllocatorBase
> client_port_allocator
=
262 protocol::ChromiumPortAllocator::Create(NULL
, network_settings
)
263 .PassAs
<cricket::HttpPortAllocatorBase
>();
265 scoped_ptr
<protocol::TransportFactory
> client_transport_factory(
266 new protocol::LibjingleTransportFactory(client_signaling_
.get(),
267 client_port_allocator
.Pass(),
270 std::vector
<protocol::AuthenticationMethod
> auth_methods
;
271 auth_methods
.push_back(protocol::AuthenticationMethod::Spake2(
272 protocol::AuthenticationMethod::NONE
));
273 scoped_ptr
<protocol::Authenticator
> client_authenticator(
274 new protocol::NegotiatingClientAuthenticator(
275 std::string(), // client_pairing_id
276 std::string(), // client_pairing_secret
277 std::string(), // authentication_tag
278 base::Bind(&ProtocolPerfTest::FetchPin
, base::Unretained(this)),
279 scoped_ptr
<protocol::ThirdPartyClientAuthenticator::TokenFetcher
>(),
281 client_
.reset(new ChromotingClient(
282 client_context_
.get(), this, this, scoped_ptr
<AudioPlayer
>()));
283 client_
->SetProtocolConfigForTests(protocol_config_
->Clone());
285 client_signaling_
.get(), client_authenticator
.Pass(),
286 client_transport_factory
.Pass(), kHostJid
, std::string());
290 bool pairing_supported
,
291 const protocol::SecretFetchedCallback
& secret_fetched_callback
) {
292 secret_fetched_callback
.Run("123456");
295 base::MessageLoopForIO message_loop_
;
297 base::Thread host_thread_
;
298 base::Thread capture_thread_
;
299 base::Thread encode_thread_
;
300 FakeDesktopEnvironmentFactory desktop_environment_factory_
;
302 scoped_ptr
<protocol::CandidateSessionConfig
> protocol_config_
;
304 scoped_ptr
<FakeSignalStrategy
> host_signaling_
;
305 scoped_ptr
<FakeSignalStrategy
> client_signaling_
;
307 scoped_ptr
<ChromotingHost
> host_
;
308 scoped_ptr
<ClientContext
> client_context_
;
309 scoped_ptr
<ChromotingClient
> client_
;
311 scoped_ptr
<base::RunLoop
> connecting_loop_
;
312 scoped_ptr
<base::RunLoop
> waiting_frames_loop_
;
314 bool client_connected_
;
315 bool host_connected_
;
317 base::Closure on_frame_task_
;
319 scoped_ptr
<VideoPacket
> last_video_packet_
;
321 DISALLOW_COPY_AND_ASSIGN(ProtocolPerfTest
);
324 TEST_F(ProtocolPerfTest
, StreamFrameRate
) {
325 StartHostAndClient(protocol::ChannelConfig::CODEC_VP8
);
326 ASSERT_NO_FATAL_FAILURE(WaitConnected());
328 base::TimeDelta latency
;
330 ReceiveFrame(&latency
);
331 LOG(INFO
) << "First frame latency: " << latency
.InMillisecondsF() << "ms";
332 ReceiveFrames(20, NULL
);
334 base::TimeTicks started
= base::TimeTicks::Now();
335 ReceiveFrames(40, &latency
);
336 base::TimeDelta elapsed
= base::TimeTicks::Now() - started
;
337 LOG(INFO
) << "Frame rate: " << (40.0 / elapsed
.InSecondsF());
338 LOG(INFO
) << "Maximum latency: " << latency
.InMillisecondsF() << "ms";
341 // Frame generator that rewrites the whole screen every 60th frame. Should only
342 // be used with the VERBATIM codec as the allocated frame may contain arbitrary
344 class IntermittentChangeFrameGenerator
345 : public base::RefCountedThreadSafe
<IntermittentChangeFrameGenerator
> {
347 IntermittentChangeFrameGenerator()
350 scoped_ptr
<webrtc::DesktopFrame
> GenerateFrame(
351 webrtc::ScreenCapturer::Callback
* callback
) {
352 const int kWidth
= 800;
353 const int kHeight
= 600;
355 bool fresh_frame
= false;
356 if (frame_index_
% 60 == 0 || !current_frame_
) {
357 current_frame_
.reset(webrtc::SharedDesktopFrame::Wrap(
358 new webrtc::BasicDesktopFrame(webrtc::DesktopSize(kWidth
, kHeight
))));
363 scoped_ptr
<webrtc::DesktopFrame
> result(current_frame_
->Share());
364 result
->mutable_updated_region()->Clear();
366 result
->mutable_updated_region()->AddRect(
367 webrtc::DesktopRect::MakeXYWH(0, 0, kWidth
, kHeight
));
369 return result
.Pass();
373 ~IntermittentChangeFrameGenerator() {}
374 friend class base::RefCountedThreadSafe
<IntermittentChangeFrameGenerator
>;
377 scoped_ptr
<webrtc::SharedDesktopFrame
> current_frame_
;
379 DISALLOW_COPY_AND_ASSIGN(IntermittentChangeFrameGenerator
);
382 TEST_F(ProtocolPerfTest
, IntermittentChanges
) {
383 desktop_environment_factory_
.set_frame_generator(
384 base::Bind(&IntermittentChangeFrameGenerator::GenerateFrame
,
385 new IntermittentChangeFrameGenerator()));
387 StartHostAndClient(protocol::ChannelConfig::CODEC_VERBATIM
);
388 ASSERT_NO_FATAL_FAILURE(WaitConnected());
392 for (int i
= 0; i
< 5; ++i
) {
393 base::TimeDelta latency
;
394 ReceiveFrame(&latency
);
395 LOG(INFO
) << "Latency: " << latency
.InMillisecondsF()
396 << "ms Encode: " << last_video_packet_
->encode_time_ms()
397 << "ms Capture: " << last_video_packet_
->capture_time_ms()
402 } // namespace remoting