OLD | NEW |
(Empty) | |
| 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/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 "net/base/test_data_directory.h" |
| 13 #include "net/url_request/url_request_context_getter.h" |
| 14 #include "remoting/base/rsa_key_pair.h" |
| 15 #include "remoting/client/audio_player.h" |
| 16 #include "remoting/client/chromoting_client.h" |
| 17 #include "remoting/client/client_context.h" |
| 18 #include "remoting/client/client_user_interface.h" |
| 19 #include "remoting/client/video_renderer.h" |
| 20 #include "remoting/host/chromoting_host.h" |
| 21 #include "remoting/host/chromoting_host_context.h" |
| 22 #include "remoting/host/fake_desktop_environment.h" |
| 23 #include "remoting/host/video_scheduler.h" |
| 24 #include "remoting/protocol/chromium_port_allocator.h" |
| 25 #include "remoting/protocol/jingle_session_manager.h" |
| 26 #include "remoting/protocol/libjingle_transport_factory.h" |
| 27 #include "remoting/protocol/me2me_host_authenticator_factory.h" |
| 28 #include "remoting/protocol/negotiating_client_authenticator.h" |
| 29 #include "remoting/protocol/session_config.h" |
| 30 #include "remoting/signaling/fake_signal_strategy.h" |
| 31 #include "testing/gtest/include/gtest/gtest.h" |
| 32 |
| 33 namespace remoting { |
| 34 |
| 35 using protocol::ChannelConfig; |
| 36 |
| 37 const char kHostJid[] = "host_jid@example.com/host"; |
| 38 const char kHostOwner[] = "jane.doe@example.com"; |
| 39 const char kClientJid[] = "jane.doe@example.com/client"; |
| 40 |
| 41 class ProtocolPerfTest : public testing::Test, |
| 42 public ClientUserInterface, |
| 43 public VideoRenderer, |
| 44 public HostStatusObserver { |
| 45 public: |
| 46 ProtocolPerfTest() |
| 47 : capture_thread_("capture"), |
| 48 encode_thread_("encode") { |
| 49 VideoScheduler::EnableTimestampsForTests(); |
| 50 capture_thread_.Start(); |
| 51 encode_thread_.Start(); |
| 52 } |
| 53 virtual ~ProtocolPerfTest() { |
| 54 message_loop_.RunUntilIdle(); |
| 55 } |
| 56 |
| 57 // ClientUserInterface interface. |
| 58 virtual void OnConnectionState(protocol::ConnectionToHost::State state, |
| 59 protocol::ErrorCode error) OVERRIDE { |
| 60 if (state == protocol::ConnectionToHost::CONNECTED) { |
| 61 client_connected_ = true; |
| 62 if (host_connected_) |
| 63 connecting_loop_->Quit(); |
| 64 } |
| 65 } |
| 66 virtual void OnConnectionReady(bool ready) OVERRIDE {} |
| 67 virtual void OnRouteChanged(const std::string& channel_name, |
| 68 const protocol::TransportRoute& route) OVERRIDE { |
| 69 } |
| 70 virtual void SetCapabilities(const std::string& capabilities) OVERRIDE {} |
| 71 virtual void SetPairingResponse( |
| 72 const protocol::PairingResponse& pairing_response) OVERRIDE {} |
| 73 virtual void DeliverHostMessage( |
| 74 const protocol::ExtensionMessage& message) OVERRIDE {} |
| 75 virtual protocol::ClipboardStub* GetClipboardStub() OVERRIDE { |
| 76 return NULL; |
| 77 } |
| 78 virtual protocol::CursorShapeStub* GetCursorShapeStub() OVERRIDE { |
| 79 return NULL; |
| 80 } |
| 81 |
| 82 // VideoRenderer interface. |
| 83 virtual void Initialize(const protocol::SessionConfig& config) OVERRIDE {} |
| 84 virtual ChromotingStats* GetStats() OVERRIDE { return NULL; } |
| 85 virtual void ProcessVideoPacket(scoped_ptr<VideoPacket> video_packet, |
| 86 const base::Closure& done) OVERRIDE { |
| 87 if (video_packet->data().empty()) { |
| 88 // Ignore keep-alive packets |
| 89 done.Run(); |
| 90 return; |
| 91 } |
| 92 |
| 93 last_video_packet_ = video_packet.Pass(); |
| 94 |
| 95 if (!on_frame_task_.is_null()) |
| 96 on_frame_task_.Run(); |
| 97 |
| 98 done.Run(); |
| 99 } |
| 100 |
| 101 // HostStatusObserver interface. |
| 102 virtual void OnClientConnected(const std::string& jid) OVERRIDE { |
| 103 host_connected_ = true; |
| 104 if (client_connected_) |
| 105 connecting_loop_->Quit(); |
| 106 } |
| 107 |
| 108 protected: |
| 109 void WaitConnected() { |
| 110 client_connected_ = false; |
| 111 host_connected_ = false; |
| 112 |
| 113 connecting_loop_.reset(new base::RunLoop()); |
| 114 connecting_loop_->Run(); |
| 115 |
| 116 ASSERT_TRUE(client_connected_ && host_connected_); |
| 117 } |
| 118 |
| 119 void ReceiveFrame(base::TimeDelta* latency) { |
| 120 waiting_frames_loop_.reset(new base::RunLoop()); |
| 121 on_frame_task_ = waiting_frames_loop_->QuitClosure(); |
| 122 waiting_frames_loop_->Run(); |
| 123 |
| 124 if (latency) { |
| 125 base::TimeTicks timestamp = |
| 126 base::TimeTicks::FromInternalValue(last_video_packet_->timestamp()); |
| 127 *latency = base::TimeTicks::Now() - timestamp; |
| 128 } |
| 129 } |
| 130 |
| 131 void ReceiveFrames(int frames, base::TimeDelta* max_latency) { |
| 132 if (max_latency) |
| 133 *max_latency = base::TimeDelta(); |
| 134 |
| 135 for (int i = 0; i < frames; ++i) { |
| 136 base::TimeDelta latency; |
| 137 |
| 138 ReceiveFrame(&latency); |
| 139 |
| 140 if (max_latency && latency > *max_latency) { |
| 141 *max_latency = latency; |
| 142 } |
| 143 } |
| 144 } |
| 145 |
| 146 void StartHostAndClient(protocol::ChannelConfig::Codec video_codec) { |
| 147 host_signaling_.reset(new FakeSignalStrategy(kHostJid)); |
| 148 client_signaling_.reset(new FakeSignalStrategy(kClientJid)); |
| 149 FakeSignalStrategy::Connect(host_signaling_.get(), client_signaling_.get()); |
| 150 |
| 151 protocol::NetworkSettings network_settings( |
| 152 protocol::NetworkSettings::NAT_TRAVERSAL_OUTGOING); |
| 153 |
| 154 scoped_ptr<protocol::CandidateSessionConfig> protocol_config = |
| 155 protocol::CandidateSessionConfig::CreateDefault(); |
| 156 protocol_config->DisableAudioChannel(); |
| 157 protocol_config->mutable_video_configs()->clear(); |
| 158 protocol_config->mutable_video_configs()->push_back(protocol::ChannelConfig( |
| 159 protocol::ChannelConfig::TRANSPORT_STREAM, 2, video_codec)); |
| 160 |
| 161 // TODO(sergeyu): Replace with a fake port allocator. |
| 162 scoped_ptr<cricket::HttpPortAllocatorBase> host_port_allocator = |
| 163 protocol::ChromiumPortAllocator::Create(NULL, network_settings) |
| 164 .PassAs<cricket::HttpPortAllocatorBase>(); |
| 165 |
| 166 scoped_ptr<protocol::TransportFactory> host_transport_factory( |
| 167 new protocol::LibjingleTransportFactory( |
| 168 host_signaling_.get(), |
| 169 host_port_allocator.Pass(), |
| 170 network_settings)); |
| 171 |
| 172 scoped_ptr<protocol::SessionManager> session_manager( |
| 173 new protocol::JingleSessionManager(host_transport_factory.Pass())); |
| 174 |
| 175 // Encoder runs on a separate thread, main thread is used for everything |
| 176 // else. |
| 177 host_.reset(new ChromotingHost(host_signaling_.get(), |
| 178 &desktop_environment_factory_, |
| 179 session_manager.Pass(), |
| 180 message_loop_.message_loop_proxy(), |
| 181 message_loop_.message_loop_proxy(), |
| 182 capture_thread_.message_loop_proxy(), |
| 183 encode_thread_.message_loop_proxy(), |
| 184 message_loop_.message_loop_proxy(), |
| 185 message_loop_.message_loop_proxy())); |
| 186 |
| 187 base::FilePath certs_dir(net::GetTestCertsDirectory()); |
| 188 |
| 189 std::string host_cert; |
| 190 ASSERT_TRUE(base::ReadFileToString( |
| 191 certs_dir.AppendASCII("unittest.selfsigned.der"), &host_cert)); |
| 192 |
| 193 base::FilePath key_path = certs_dir.AppendASCII("unittest.key.bin"); |
| 194 std::string key_string; |
| 195 ASSERT_TRUE(base::ReadFileToString(key_path, &key_string)); |
| 196 std::string key_base64; |
| 197 base::Base64Encode(key_string, &key_base64); |
| 198 scoped_refptr<RsaKeyPair> key_pair = RsaKeyPair::FromString(key_base64); |
| 199 ASSERT_TRUE(key_pair.get()); |
| 200 |
| 201 |
| 202 protocol::SharedSecretHash host_secret; |
| 203 host_secret.hash_function = protocol::AuthenticationMethod::NONE; |
| 204 host_secret.value = "123456"; |
| 205 scoped_ptr<protocol::AuthenticatorFactory> auth_factory = |
| 206 protocol::Me2MeHostAuthenticatorFactory::CreateWithSharedSecret( |
| 207 true, kHostOwner, host_cert, key_pair, host_secret, NULL); |
| 208 host_->SetAuthenticatorFactory(auth_factory.Pass()); |
| 209 |
| 210 host_->AddStatusObserver(this); |
| 211 host_->set_protocol_config(protocol_config->Clone()); |
| 212 host_->Start(kHostOwner); |
| 213 |
| 214 // Initialize client. |
| 215 client_context_.reset( |
| 216 new ClientContext(base::ThreadTaskRunnerHandle::Get())); |
| 217 |
| 218 // TODO(sergeyu): Replace with a fake port allocator |
| 219 scoped_ptr<cricket::HttpPortAllocatorBase> client_port_allocator = |
| 220 protocol::ChromiumPortAllocator::Create(NULL, network_settings) |
| 221 .PassAs<cricket::HttpPortAllocatorBase>(); |
| 222 |
| 223 scoped_ptr<protocol::TransportFactory> client_transport_factory( |
| 224 new protocol::LibjingleTransportFactory(client_signaling_.get(), |
| 225 client_port_allocator.Pass(), |
| 226 network_settings)); |
| 227 |
| 228 std::vector<protocol::AuthenticationMethod> auth_methods; |
| 229 auth_methods.push_back(protocol::AuthenticationMethod::Spake2( |
| 230 protocol::AuthenticationMethod::NONE)); |
| 231 scoped_ptr<protocol::Authenticator> client_authenticator( |
| 232 new protocol::NegotiatingClientAuthenticator( |
| 233 std::string(), // client_pairing_id |
| 234 std::string(), // client_pairing_secret |
| 235 std::string(), // authentication_tag |
| 236 base::Bind(&ProtocolPerfTest::FetchPin, base::Unretained(this)), |
| 237 scoped_ptr<protocol::ThirdPartyClientAuthenticator::TokenFetcher>(), |
| 238 auth_methods)); |
| 239 client_.reset(new ChromotingClient( |
| 240 client_context_.get(), this, this, scoped_ptr<AudioPlayer>())); |
| 241 client_->SetProtocolConfigForTests(protocol_config->Clone()); |
| 242 client_->Start( |
| 243 client_signaling_.get(), client_authenticator.Pass(), |
| 244 client_transport_factory.Pass(), kHostJid, std::string()); |
| 245 } |
| 246 |
| 247 void FetchPin( |
| 248 bool pairing_supported, |
| 249 const protocol::SecretFetchedCallback& secret_fetched_callback) { |
| 250 secret_fetched_callback.Run("123456"); |
| 251 } |
| 252 |
| 253 base::MessageLoopForIO message_loop_; |
| 254 |
| 255 FakeDesktopEnvironmentFactory desktop_environment_factory_; |
| 256 base::Thread capture_thread_; |
| 257 base::Thread encode_thread_; |
| 258 |
| 259 scoped_ptr<FakeSignalStrategy> host_signaling_; |
| 260 scoped_ptr<FakeSignalStrategy> client_signaling_; |
| 261 |
| 262 scoped_ptr<ChromotingHost> host_; |
| 263 scoped_ptr<ClientContext> client_context_; |
| 264 scoped_ptr<ChromotingClient> client_; |
| 265 |
| 266 scoped_ptr<base::RunLoop> connecting_loop_; |
| 267 scoped_ptr<base::RunLoop> waiting_frames_loop_; |
| 268 |
| 269 bool client_connected_; |
| 270 bool host_connected_; |
| 271 |
| 272 base::Closure on_frame_task_; |
| 273 |
| 274 scoped_ptr<VideoPacket> last_video_packet_; |
| 275 |
| 276 DISALLOW_COPY_AND_ASSIGN(ProtocolPerfTest); |
| 277 }; |
| 278 |
| 279 TEST_F(ProtocolPerfTest, StreamFrameRate) { |
| 280 StartHostAndClient(protocol::ChannelConfig::CODEC_VP8); |
| 281 ASSERT_NO_FATAL_FAILURE(WaitConnected()); |
| 282 |
| 283 base::TimeDelta latency; |
| 284 |
| 285 ReceiveFrame(&latency); |
| 286 LOG(INFO) << "First frame latency: " << latency.InMillisecondsF() << "ms"; |
| 287 ReceiveFrames(20, NULL); |
| 288 |
| 289 base::TimeTicks started = base::TimeTicks::Now(); |
| 290 ReceiveFrames(40, &latency); |
| 291 base::TimeDelta elapsed = base::TimeTicks::Now() - started; |
| 292 LOG(INFO) << "Frame rate: " << (40.0 / elapsed.InSecondsF()); |
| 293 LOG(INFO) << "Maximum latency: " << latency.InMillisecondsF() << "ms"; |
| 294 } |
| 295 |
| 296 // Frame generator that rewrites the whole screen every 60th frame. Should only |
| 297 // be used with the VERBATIM codec as the allocated frame may contain arbitrary |
| 298 // data. |
| 299 class IntermittentChangeFrameGenerator |
| 300 : public base::RefCountedThreadSafe<IntermittentChangeFrameGenerator> { |
| 301 public: |
| 302 IntermittentChangeFrameGenerator() |
| 303 : frame_index_(0) {} |
| 304 |
| 305 scoped_ptr<webrtc::DesktopFrame> GenerateFrame( |
| 306 webrtc::ScreenCapturer::Callback* callback) { |
| 307 const int kWidth = 800; |
| 308 const int kHeight = 600; |
| 309 |
| 310 bool fresh_frame = false; |
| 311 if (frame_index_ % 60 == 0 || !current_frame_) { |
| 312 current_frame_.reset(webrtc::SharedDesktopFrame::Wrap( |
| 313 new webrtc::BasicDesktopFrame(webrtc::DesktopSize(kWidth, kHeight)))); |
| 314 fresh_frame = true; |
| 315 } |
| 316 ++frame_index_; |
| 317 |
| 318 scoped_ptr<webrtc::DesktopFrame> result(current_frame_->Share()); |
| 319 result->mutable_updated_region()->Clear(); |
| 320 if (fresh_frame) { |
| 321 result->mutable_updated_region()->AddRect( |
| 322 webrtc::DesktopRect::MakeXYWH(0, 0, kWidth, kHeight)); |
| 323 } |
| 324 return result.Pass(); |
| 325 } |
| 326 |
| 327 private: |
| 328 ~IntermittentChangeFrameGenerator() {} |
| 329 friend class base::RefCountedThreadSafe<IntermittentChangeFrameGenerator>; |
| 330 |
| 331 int frame_index_; |
| 332 scoped_ptr<webrtc::SharedDesktopFrame> current_frame_; |
| 333 |
| 334 DISALLOW_COPY_AND_ASSIGN(IntermittentChangeFrameGenerator); |
| 335 }; |
| 336 |
| 337 TEST_F(ProtocolPerfTest, IntermittentChanges) { |
| 338 desktop_environment_factory_.set_frame_generator( |
| 339 base::Bind(&IntermittentChangeFrameGenerator::GenerateFrame, |
| 340 new IntermittentChangeFrameGenerator())); |
| 341 |
| 342 StartHostAndClient(protocol::ChannelConfig::CODEC_VERBATIM); |
| 343 ASSERT_NO_FATAL_FAILURE(WaitConnected()); |
| 344 |
| 345 ReceiveFrame(NULL); |
| 346 |
| 347 for (int i = 0; i < 5; ++i) { |
| 348 base::TimeDelta latency; |
| 349 ReceiveFrame(&latency); |
| 350 LOG(INFO) << "Latency: " << latency.InMillisecondsF() |
| 351 << "ms Encode: " << last_video_packet_->encode_time_ms() |
| 352 << "ms Capture: " << last_video_packet_->capture_time_ms() |
| 353 << "ms"; |
| 354 } |
| 355 } |
| 356 |
| 357 } // namespace remoting |
OLD | NEW |