• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "base/base64.h"
6 #include "base/files/file_util.h"
7 #include "base/message_loop/message_loop.h"
8 #include "base/rand_util.h"
9 #include "base/run_loop.h"
10 #include "base/single_thread_task_runner.h"
11 #include "base/synchronization/waitable_event.h"
12 #include "base/thread_task_runner_handle.h"
13 #include "jingle/glue/thread_wrapper.h"
14 #include "net/base/test_data_directory.h"
15 #include "net/url_request/url_request_context_getter.h"
16 #include "remoting/base/rsa_key_pair.h"
17 #include "remoting/client/audio_player.h"
18 #include "remoting/client/chromoting_client.h"
19 #include "remoting/client/client_context.h"
20 #include "remoting/client/client_user_interface.h"
21 #include "remoting/client/video_renderer.h"
22 #include "remoting/host/chromoting_host.h"
23 #include "remoting/host/chromoting_host_context.h"
24 #include "remoting/host/fake_desktop_environment.h"
25 #include "remoting/host/video_scheduler.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 "remoting/test/fake_network_dispatcher.h"
33 #include "remoting/test/fake_port_allocator.h"
34 #include "remoting/test/fake_socket_factory.h"
35 #include "testing/gtest/include/gtest/gtest.h"
36 
37 namespace remoting {
38 
39 using protocol::ChannelConfig;
40 
41 const char kHostJid[] = "host_jid@example.com/host";
42 const char kHostOwner[] = "jane.doe@example.com";
43 const char kClientJid[] = "jane.doe@example.com/client";
44 
45 struct NetworkPerformanceParams {
NetworkPerformanceParamsremoting::NetworkPerformanceParams46   NetworkPerformanceParams(int bandwidth,
47                            int max_buffers,
48                            double latency_average_ms,
49                            double latency_stddev_ms,
50                            double out_of_order_rate)
51       : bandwidth(bandwidth),
52         max_buffers(max_buffers),
53         latency_average(base::TimeDelta::FromMillisecondsD(latency_average_ms)),
54         latency_stddev(base::TimeDelta::FromMillisecondsD(latency_stddev_ms)),
55         out_of_order_rate(out_of_order_rate) {}
56 
57   int bandwidth;
58   int max_buffers;
59   base::TimeDelta latency_average;
60   base::TimeDelta latency_stddev;
61   double out_of_order_rate;
62 };
63 
64 class FakeCursorShapeStub : public protocol::CursorShapeStub {
65  public:
FakeCursorShapeStub()66   FakeCursorShapeStub() {}
~FakeCursorShapeStub()67   virtual ~FakeCursorShapeStub() {}
68 
69   // protocol::CursorShapeStub interface.
SetCursorShape(const protocol::CursorShapeInfo & cursor_shape)70   virtual void SetCursorShape(
71       const protocol::CursorShapeInfo& cursor_shape) OVERRIDE {};
72 };
73 
74 class ProtocolPerfTest
75     : public testing::Test,
76       public testing::WithParamInterface<NetworkPerformanceParams>,
77       public ClientUserInterface,
78       public VideoRenderer,
79       public HostStatusObserver {
80  public:
ProtocolPerfTest()81   ProtocolPerfTest()
82       : host_thread_("host"),
83         capture_thread_("capture"),
84         encode_thread_("encode") {
85     VideoScheduler::EnableTimestampsForTests();
86     host_thread_.StartWithOptions(
87         base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
88     capture_thread_.Start();
89     encode_thread_.Start();
90   }
91 
~ProtocolPerfTest()92   virtual ~ProtocolPerfTest() {
93     host_thread_.message_loop_proxy()->DeleteSoon(FROM_HERE, host_.release());
94     host_thread_.message_loop_proxy()->DeleteSoon(FROM_HERE,
95                                                   host_signaling_.release());
96     message_loop_.RunUntilIdle();
97   }
98 
99   // ClientUserInterface interface.
OnConnectionState(protocol::ConnectionToHost::State state,protocol::ErrorCode error)100   virtual void OnConnectionState(protocol::ConnectionToHost::State state,
101                                  protocol::ErrorCode error) OVERRIDE {
102     if (state == protocol::ConnectionToHost::CONNECTED) {
103       client_connected_ = true;
104       if (host_connected_)
105         connecting_loop_->Quit();
106     }
107   }
OnConnectionReady(bool ready)108   virtual void OnConnectionReady(bool ready) OVERRIDE {}
OnRouteChanged(const std::string & channel_name,const protocol::TransportRoute & route)109   virtual void OnRouteChanged(const std::string& channel_name,
110                               const protocol::TransportRoute& route) OVERRIDE {
111   }
SetCapabilities(const std::string & capabilities)112   virtual void SetCapabilities(const std::string& capabilities) OVERRIDE {}
SetPairingResponse(const protocol::PairingResponse & pairing_response)113   virtual void SetPairingResponse(
114       const protocol::PairingResponse& pairing_response) OVERRIDE {}
DeliverHostMessage(const protocol::ExtensionMessage & message)115   virtual void DeliverHostMessage(
116       const protocol::ExtensionMessage& message) OVERRIDE {}
GetClipboardStub()117   virtual protocol::ClipboardStub* GetClipboardStub() OVERRIDE {
118     return NULL;
119   }
GetCursorShapeStub()120   virtual protocol::CursorShapeStub* GetCursorShapeStub() OVERRIDE {
121     return &cursor_shape_stub_;
122   }
123 
124   // VideoRenderer interface.
Initialize(const protocol::SessionConfig & config)125   virtual void Initialize(const protocol::SessionConfig& config) OVERRIDE {}
GetStats()126   virtual ChromotingStats* GetStats() OVERRIDE { return NULL; }
ProcessVideoPacket(scoped_ptr<VideoPacket> video_packet,const base::Closure & done)127   virtual void ProcessVideoPacket(scoped_ptr<VideoPacket> video_packet,
128                                   const base::Closure& done) OVERRIDE {
129     if (video_packet->data().empty()) {
130       // Ignore keep-alive packets
131       done.Run();
132       return;
133     }
134 
135     last_video_packet_ = video_packet.Pass();
136 
137     if (!on_frame_task_.is_null())
138       on_frame_task_.Run();
139 
140     done.Run();
141   }
142 
143   // HostStatusObserver interface.
OnClientConnected(const std::string & jid)144   virtual void OnClientConnected(const std::string& jid) OVERRIDE {
145     message_loop_.PostTask(
146         FROM_HERE,
147         base::Bind(&ProtocolPerfTest::OnHostConnectedMainThread,
148                    base::Unretained(this)));
149   }
150 
151  protected:
WaitConnected()152   void WaitConnected() {
153     client_connected_ = false;
154     host_connected_ = false;
155 
156     connecting_loop_.reset(new base::RunLoop());
157     connecting_loop_->Run();
158 
159     ASSERT_TRUE(client_connected_ && host_connected_);
160   }
161 
OnHostConnectedMainThread()162   void OnHostConnectedMainThread() {
163     host_connected_ = true;
164     if (client_connected_)
165       connecting_loop_->Quit();
166   }
167 
ReceiveFrame(base::TimeDelta * latency)168   void ReceiveFrame(base::TimeDelta* latency) {
169     waiting_frames_loop_.reset(new base::RunLoop());
170     on_frame_task_ = waiting_frames_loop_->QuitClosure();
171     waiting_frames_loop_->Run();
172 
173     if (latency) {
174       base::TimeTicks timestamp =
175           base::TimeTicks::FromInternalValue(last_video_packet_->timestamp());
176       *latency = base::TimeTicks::Now() - timestamp;
177     }
178   }
179 
ReceiveFrames(int frames,base::TimeDelta * max_latency)180   void ReceiveFrames(int frames, base::TimeDelta* max_latency) {
181     if (max_latency)
182       *max_latency = base::TimeDelta();
183 
184     for (int i = 0; i < frames; ++i) {
185       base::TimeDelta latency;
186 
187       ReceiveFrame(&latency);
188 
189       if (max_latency && latency > *max_latency) {
190         *max_latency = latency;
191       }
192     }
193   }
194 
195   // Creates test host and client and starts connection between them. Caller
196   // should call WaitConnected() to wait until connection is established. The
197   // host is started on |host_thread_| while the client works on the main
198   // thread.
StartHostAndClient(protocol::ChannelConfig::Codec video_codec)199   void StartHostAndClient(protocol::ChannelConfig::Codec video_codec) {
200     fake_network_dispatcher_ =  new FakeNetworkDispatcher();
201 
202     client_signaling_.reset(new FakeSignalStrategy(kClientJid));
203 
204     jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
205 
206     protocol_config_ = protocol::CandidateSessionConfig::CreateDefault();
207     protocol_config_->DisableAudioChannel();
208     protocol_config_->mutable_video_configs()->clear();
209     protocol_config_->mutable_video_configs()->push_back(
210         protocol::ChannelConfig(
211             protocol::ChannelConfig::TRANSPORT_STREAM, 2, video_codec));
212 
213     host_thread_.message_loop_proxy()->PostTask(
214         FROM_HERE,
215         base::Bind(&ProtocolPerfTest::StartHost, base::Unretained(this)));
216   }
217 
StartHost()218   void StartHost() {
219     DCHECK(host_thread_.message_loop_proxy()->BelongsToCurrentThread());
220 
221     jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
222 
223     host_signaling_.reset(new FakeSignalStrategy(kHostJid));
224     host_signaling_->ConnectTo(client_signaling_.get());
225 
226     protocol::NetworkSettings network_settings(
227         protocol::NetworkSettings::NAT_TRAVERSAL_OUTGOING);
228 
229     scoped_ptr<FakePortAllocator> port_allocator(
230         FakePortAllocator::Create(fake_network_dispatcher_));
231     port_allocator->socket_factory()->SetBandwidth(GetParam().bandwidth,
232                                                    GetParam().max_buffers);
233     port_allocator->socket_factory()->SetLatency(GetParam().latency_average,
234                                                  GetParam().latency_stddev);
235     port_allocator->socket_factory()->set_out_of_order_rate(
236         GetParam().out_of_order_rate);
237     scoped_ptr<protocol::TransportFactory> host_transport_factory(
238         new protocol::LibjingleTransportFactory(
239             host_signaling_.get(),
240             port_allocator.PassAs<cricket::HttpPortAllocatorBase>(),
241             network_settings));
242 
243     scoped_ptr<protocol::SessionManager> session_manager(
244         new protocol::JingleSessionManager(host_transport_factory.Pass()));
245 
246     // Encoder runs on a separate thread, main thread is used for everything
247     // else.
248     host_.reset(new ChromotingHost(host_signaling_.get(),
249                                    &desktop_environment_factory_,
250                                    session_manager.Pass(),
251                                    host_thread_.message_loop_proxy(),
252                                    host_thread_.message_loop_proxy(),
253                                    capture_thread_.message_loop_proxy(),
254                                    encode_thread_.message_loop_proxy(),
255                                    host_thread_.message_loop_proxy(),
256                                    host_thread_.message_loop_proxy()));
257 
258     base::FilePath certs_dir(net::GetTestCertsDirectory());
259 
260     std::string host_cert;
261     ASSERT_TRUE(base::ReadFileToString(
262         certs_dir.AppendASCII("unittest.selfsigned.der"), &host_cert));
263 
264     base::FilePath key_path = certs_dir.AppendASCII("unittest.key.bin");
265     std::string key_string;
266     ASSERT_TRUE(base::ReadFileToString(key_path, &key_string));
267     std::string key_base64;
268     base::Base64Encode(key_string, &key_base64);
269     scoped_refptr<RsaKeyPair> key_pair = RsaKeyPair::FromString(key_base64);
270     ASSERT_TRUE(key_pair.get());
271 
272 
273     protocol::SharedSecretHash host_secret;
274     host_secret.hash_function = protocol::AuthenticationMethod::NONE;
275     host_secret.value = "123456";
276     scoped_ptr<protocol::AuthenticatorFactory> auth_factory =
277         protocol::Me2MeHostAuthenticatorFactory::CreateWithSharedSecret(
278             true, kHostOwner, host_cert, key_pair, host_secret, NULL);
279     host_->SetAuthenticatorFactory(auth_factory.Pass());
280 
281     host_->AddStatusObserver(this);
282     host_->set_protocol_config(protocol_config_->Clone());
283     host_->Start(kHostOwner);
284 
285     message_loop_.PostTask(FROM_HERE,
286                            base::Bind(&ProtocolPerfTest::StartClientAfterHost,
287                                       base::Unretained(this)));
288   }
289 
StartClientAfterHost()290   void StartClientAfterHost() {
291     client_signaling_->ConnectTo(host_signaling_.get());
292 
293     protocol::NetworkSettings network_settings(
294         protocol::NetworkSettings::NAT_TRAVERSAL_OUTGOING);
295 
296     // Initialize client.
297     client_context_.reset(
298         new ClientContext(base::ThreadTaskRunnerHandle::Get()));
299 
300     scoped_ptr<FakePortAllocator> port_allocator(
301         FakePortAllocator::Create(fake_network_dispatcher_));
302     port_allocator->socket_factory()->SetBandwidth(GetParam().bandwidth,
303                                                    GetParam().max_buffers);
304     port_allocator->socket_factory()->SetLatency(GetParam().latency_average,
305                                                  GetParam().latency_stddev);
306     port_allocator->socket_factory()->set_out_of_order_rate(
307         GetParam().out_of_order_rate);
308     scoped_ptr<protocol::TransportFactory> client_transport_factory(
309         new protocol::LibjingleTransportFactory(
310             client_signaling_.get(),
311             port_allocator.PassAs<cricket::HttpPortAllocatorBase>(),
312             network_settings));
313 
314     std::vector<protocol::AuthenticationMethod> auth_methods;
315     auth_methods.push_back(protocol::AuthenticationMethod::Spake2(
316         protocol::AuthenticationMethod::NONE));
317     scoped_ptr<protocol::Authenticator> client_authenticator(
318         new protocol::NegotiatingClientAuthenticator(
319             std::string(),  // client_pairing_id
320             std::string(),  // client_pairing_secret
321             std::string(),  // authentication_tag
322             base::Bind(&ProtocolPerfTest::FetchPin, base::Unretained(this)),
323             scoped_ptr<protocol::ThirdPartyClientAuthenticator::TokenFetcher>(),
324             auth_methods));
325     client_.reset(new ChromotingClient(
326         client_context_.get(), this, this, scoped_ptr<AudioPlayer>()));
327     client_->SetProtocolConfigForTests(protocol_config_->Clone());
328     client_->Start(
329         client_signaling_.get(), client_authenticator.Pass(),
330         client_transport_factory.Pass(), kHostJid, std::string());
331   }
332 
FetchPin(bool pairing_supported,const protocol::SecretFetchedCallback & secret_fetched_callback)333   void FetchPin(
334       bool pairing_supported,
335       const protocol::SecretFetchedCallback& secret_fetched_callback) {
336     secret_fetched_callback.Run("123456");
337   }
338 
339   base::MessageLoopForIO message_loop_;
340 
341   scoped_refptr<FakeNetworkDispatcher> fake_network_dispatcher_;
342 
343   base::Thread host_thread_;
344   base::Thread capture_thread_;
345   base::Thread encode_thread_;
346   FakeDesktopEnvironmentFactory desktop_environment_factory_;
347 
348   FakeCursorShapeStub cursor_shape_stub_;
349 
350   scoped_ptr<protocol::CandidateSessionConfig> protocol_config_;
351 
352   scoped_ptr<FakeSignalStrategy> host_signaling_;
353   scoped_ptr<FakeSignalStrategy> client_signaling_;
354 
355   scoped_ptr<ChromotingHost> host_;
356   scoped_ptr<ClientContext> client_context_;
357   scoped_ptr<ChromotingClient> client_;
358 
359   scoped_ptr<base::RunLoop> connecting_loop_;
360   scoped_ptr<base::RunLoop> waiting_frames_loop_;
361 
362   bool client_connected_;
363   bool host_connected_;
364 
365   base::Closure on_frame_task_;
366 
367   scoped_ptr<VideoPacket> last_video_packet_;
368 
369   DISALLOW_COPY_AND_ASSIGN(ProtocolPerfTest);
370 };
371 
372 INSTANTIATE_TEST_CASE_P(
373     NoDelay,
374     ProtocolPerfTest,
375     ::testing::Values(NetworkPerformanceParams(0, 0, 0, 0, 0.0)));
376 
377 INSTANTIATE_TEST_CASE_P(
378     HighLatency,
379     ProtocolPerfTest,
380     ::testing::Values(NetworkPerformanceParams(0, 0, 300, 30, 0.0),
381                       NetworkPerformanceParams(0, 0, 30, 10, 0.0)));
382 
383 INSTANTIATE_TEST_CASE_P(
384     OutOfOrder,
385     ProtocolPerfTest,
386     ::testing::Values(NetworkPerformanceParams(0, 0, 2, 0, 0.01),
387                       NetworkPerformanceParams(0, 0, 30, 1, 0.01),
388                       NetworkPerformanceParams(0, 0, 30, 1, 0.1),
389                       NetworkPerformanceParams(0, 0, 300, 20, 0.01),
390                       NetworkPerformanceParams(0, 0, 300, 20, 0.1)));
391 
392 INSTANTIATE_TEST_CASE_P(
393     LimitedBandwidth,
394     ProtocolPerfTest,
395     ::testing::Values(
396         // 100 MBps
397         NetworkPerformanceParams(800000000, 800000000, 2, 1, 0.0),
398         // 8 MBps
399         NetworkPerformanceParams(1000000, 300000, 30, 5, 0.01),
400         NetworkPerformanceParams(1000000, 2000000, 30, 5, 0.01),
401         // 800 kBps
402         NetworkPerformanceParams(100000, 30000, 130, 5, 0.01),
403         NetworkPerformanceParams(100000, 200000, 130, 5, 0.01)));
404 
TEST_P(ProtocolPerfTest,StreamFrameRate)405 TEST_P(ProtocolPerfTest, StreamFrameRate) {
406   StartHostAndClient(protocol::ChannelConfig::CODEC_VP8);
407   ASSERT_NO_FATAL_FAILURE(WaitConnected());
408 
409   base::TimeDelta latency;
410 
411   ReceiveFrame(&latency);
412   LOG(INFO) << "First frame latency: " << latency.InMillisecondsF() << "ms";
413   ReceiveFrames(20, NULL);
414 
415   base::TimeTicks started = base::TimeTicks::Now();
416   ReceiveFrames(40, &latency);
417   base::TimeDelta elapsed = base::TimeTicks::Now() - started;
418   LOG(INFO) << "Frame rate: " << (40.0 / elapsed.InSecondsF());
419   LOG(INFO) << "Maximum latency: " << latency.InMillisecondsF() << "ms";
420 }
421 
422 const int kIntermittentFrameSize = 100 * 1000;
423 
424 // Frame generator that rewrites the whole screen every 60th frame. Should only
425 // be used with the VERBATIM codec as the allocated frame may contain arbitrary
426 // data.
427 class IntermittentChangeFrameGenerator
428     : public base::RefCountedThreadSafe<IntermittentChangeFrameGenerator> {
429  public:
IntermittentChangeFrameGenerator()430   IntermittentChangeFrameGenerator()
431       : frame_index_(0) {}
432 
GenerateFrame(webrtc::DesktopCapturer::Callback * callback)433   scoped_ptr<webrtc::DesktopFrame> GenerateFrame(
434       webrtc::DesktopCapturer::Callback* callback) {
435     const int kWidth = 1000;
436     const int kHeight = kIntermittentFrameSize / kWidth / 4;
437 
438     bool fresh_frame = false;
439     if (frame_index_ % 60 == 0 || !current_frame_) {
440       current_frame_.reset(webrtc::SharedDesktopFrame::Wrap(
441           new webrtc::BasicDesktopFrame(webrtc::DesktopSize(kWidth, kHeight))));
442       fresh_frame = true;
443     }
444     ++frame_index_;
445 
446     scoped_ptr<webrtc::DesktopFrame> result(current_frame_->Share());
447     result->mutable_updated_region()->Clear();
448     if (fresh_frame) {
449       result->mutable_updated_region()->AddRect(
450           webrtc::DesktopRect::MakeXYWH(0, 0, kWidth, kHeight));
451     }
452     return result.Pass();
453   }
454 
455  private:
~IntermittentChangeFrameGenerator()456   ~IntermittentChangeFrameGenerator() {}
457   friend class base::RefCountedThreadSafe<IntermittentChangeFrameGenerator>;
458 
459   int frame_index_;
460   scoped_ptr<webrtc::SharedDesktopFrame> current_frame_;
461 
462   DISALLOW_COPY_AND_ASSIGN(IntermittentChangeFrameGenerator);
463 };
464 
TEST_P(ProtocolPerfTest,IntermittentChanges)465 TEST_P(ProtocolPerfTest, IntermittentChanges) {
466   desktop_environment_factory_.set_frame_generator(
467       base::Bind(&IntermittentChangeFrameGenerator::GenerateFrame,
468                  new IntermittentChangeFrameGenerator()));
469 
470   StartHostAndClient(protocol::ChannelConfig::CODEC_VERBATIM);
471   ASSERT_NO_FATAL_FAILURE(WaitConnected());
472 
473   ReceiveFrame(NULL);
474 
475   base::TimeDelta expected = GetParam().latency_average;
476   if (GetParam().bandwidth > 0) {
477     expected += base::TimeDelta::FromSecondsD(kIntermittentFrameSize /
478                                               GetParam().bandwidth);
479   }
480   LOG(INFO) << "Expected: " << expected.InMillisecondsF() << "ms";
481 
482   base::TimeDelta sum;
483 
484   const int kFrames = 5;
485   for (int i = 0; i < kFrames; ++i) {
486     base::TimeDelta latency;
487     ReceiveFrame(&latency);
488     LOG(INFO) << "Latency: " << latency.InMillisecondsF()
489               << "ms Encode: " << last_video_packet_->encode_time_ms()
490               << "ms Capture: " << last_video_packet_->capture_time_ms()
491               << "ms";
492     sum += latency;
493   }
494 
495   LOG(INFO) << "Average: " << (sum / kFrames).InMillisecondsF();
496 }
497 
498 }  // namespace remoting
499