| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2010 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/environment.h" | |
| 6 #include "base/message_loop.h" | |
| 7 #include "base/process_util.h" | |
| 8 #include "base/scoped_ptr.h" | |
| 9 #include "base/sync_socket.h" | |
| 10 #include "chrome/browser/browser_thread.h" | |
| 11 #include "chrome/browser/renderer_host/audio_renderer_host.h" | |
| 12 #include "chrome/common/render_messages.h" | |
| 13 #include "chrome/common/render_messages_params.h" | |
| 14 #include "ipc/ipc_message_utils.h" | |
| 15 #include "media/audio/audio_manager.h" | |
| 16 #include "media/audio/fake_audio_output_stream.h" | |
| 17 #include "testing/gmock/include/gmock/gmock.h" | |
| 18 #include "testing/gtest/include/gtest/gtest.h" | |
| 19 | |
| 20 using ::testing::_; | |
| 21 using ::testing::DoAll; | |
| 22 using ::testing::InSequence; | |
| 23 using ::testing::InvokeWithoutArgs; | |
| 24 using ::testing::Return; | |
| 25 using ::testing::SaveArg; | |
| 26 using ::testing::SetArgumentPointee; | |
| 27 | |
| 28 static const int kInvalidId = -1; | |
| 29 static const int kRouteId = 200; | |
| 30 static const int kStreamId = 50; | |
| 31 | |
| 32 static bool IsRunningHeadless() { | |
| 33 scoped_ptr<base::Environment> env(base::Environment::Create()); | |
| 34 if (env->HasVar("CHROME_HEADLESS")) | |
| 35 return true; | |
| 36 return false; | |
| 37 } | |
| 38 | |
| 39 class MockAudioRendererHost : public AudioRendererHost { | |
| 40 public: | |
| 41 MockAudioRendererHost() : shared_memory_length_(0) { | |
| 42 } | |
| 43 | |
| 44 virtual ~MockAudioRendererHost() { | |
| 45 } | |
| 46 | |
| 47 // A list of mock methods. | |
| 48 MOCK_METHOD3(OnRequestPacket, | |
| 49 void(int routing_id, int stream_id, | |
| 50 AudioBuffersState buffers_state)); | |
| 51 MOCK_METHOD3(OnStreamCreated, | |
| 52 void(int routing_id, int stream_id, int length)); | |
| 53 MOCK_METHOD3(OnLowLatencyStreamCreated, | |
| 54 void(int routing_id, int stream_id, int length)); | |
| 55 MOCK_METHOD2(OnStreamPlaying, void(int routing_id, int stream_id)); | |
| 56 MOCK_METHOD2(OnStreamPaused, void(int routing_id, int stream_id)); | |
| 57 MOCK_METHOD2(OnStreamError, void(int routing_id, int stream_id)); | |
| 58 MOCK_METHOD3(OnStreamVolume, | |
| 59 void(int routing_id, int stream_id, double volume)); | |
| 60 | |
| 61 base::SharedMemory* shared_memory() { return shared_memory_.get(); } | |
| 62 uint32 shared_memory_length() { return shared_memory_length_; } | |
| 63 | |
| 64 base::SyncSocket* sync_socket() { return sync_socket_.get(); } | |
| 65 | |
| 66 private: | |
| 67 // This method is used to dispatch IPC messages to the renderer. We intercept | |
| 68 // these messages here and dispatch to our mock methods to verify the | |
| 69 // conversation between this object and the renderer. | |
| 70 virtual bool Send(IPC::Message* message) { | |
| 71 CHECK(message); | |
| 72 | |
| 73 // In this method we dispatch the messages to the according handlers as if | |
| 74 // we are the renderer. | |
| 75 bool handled = true; | |
| 76 IPC_BEGIN_MESSAGE_MAP(MockAudioRendererHost, *message) | |
| 77 IPC_MESSAGE_HANDLER(ViewMsg_RequestAudioPacket, OnRequestPacket) | |
| 78 IPC_MESSAGE_HANDLER(ViewMsg_NotifyAudioStreamCreated, OnStreamCreated) | |
| 79 IPC_MESSAGE_HANDLER(ViewMsg_NotifyLowLatencyAudioStreamCreated, | |
| 80 OnLowLatencyStreamCreated) | |
| 81 IPC_MESSAGE_HANDLER(ViewMsg_NotifyAudioStreamStateChanged, | |
| 82 OnStreamStateChanged) | |
| 83 IPC_MESSAGE_HANDLER(ViewMsg_NotifyAudioStreamVolume, OnStreamVolume) | |
| 84 IPC_MESSAGE_UNHANDLED(handled = false) | |
| 85 IPC_END_MESSAGE_MAP() | |
| 86 EXPECT_TRUE(handled); | |
| 87 | |
| 88 delete message; | |
| 89 return true; | |
| 90 } | |
| 91 | |
| 92 // These handler methods do minimal things and delegate to the mock methods. | |
| 93 void OnRequestPacket(const IPC::Message& msg, int stream_id, | |
| 94 AudioBuffersState buffers_state) { | |
| 95 OnRequestPacket(msg.routing_id(), stream_id, buffers_state); | |
| 96 } | |
| 97 | |
| 98 void OnStreamCreated(const IPC::Message& msg, int stream_id, | |
| 99 base::SharedMemoryHandle handle, uint32 length) { | |
| 100 // Maps the shared memory. | |
| 101 shared_memory_.reset(new base::SharedMemory(handle, false)); | |
| 102 ASSERT_TRUE(shared_memory_->Map(length)); | |
| 103 ASSERT_TRUE(shared_memory_->memory()); | |
| 104 shared_memory_length_ = length; | |
| 105 | |
| 106 // And then delegate the call to the mock method. | |
| 107 OnStreamCreated(msg.routing_id(), stream_id, length); | |
| 108 } | |
| 109 | |
| 110 void OnLowLatencyStreamCreated(const IPC::Message& msg, int stream_id, | |
| 111 base::SharedMemoryHandle handle, | |
| 112 #if defined(OS_WIN) | |
| 113 base::SyncSocket::Handle socket_handle, | |
| 114 #else | |
| 115 base::FileDescriptor socket_descriptor, | |
| 116 #endif | |
| 117 uint32 length) { | |
| 118 // Maps the shared memory. | |
| 119 shared_memory_.reset(new base::SharedMemory(handle, false)); | |
| 120 CHECK(shared_memory_->Map(length)); | |
| 121 CHECK(shared_memory_->memory()); | |
| 122 shared_memory_length_ = length; | |
| 123 | |
| 124 // Create the SyncSocket using the handle. | |
| 125 base::SyncSocket::Handle sync_socket_handle; | |
| 126 #if defined(OS_WIN) | |
| 127 sync_socket_handle = socket_handle; | |
| 128 #else | |
| 129 sync_socket_handle = socket_descriptor.fd; | |
| 130 #endif | |
| 131 sync_socket_.reset(new base::SyncSocket(sync_socket_handle)); | |
| 132 | |
| 133 // And then delegate the call to the mock method. | |
| 134 OnLowLatencyStreamCreated(msg.routing_id(), stream_id, length); | |
| 135 } | |
| 136 | |
| 137 void OnStreamStateChanged(const IPC::Message& msg, int stream_id, | |
| 138 const ViewMsg_AudioStreamState_Params& params) { | |
| 139 if (params.state == ViewMsg_AudioStreamState_Params::kPlaying) { | |
| 140 OnStreamPlaying(msg.routing_id(), stream_id); | |
| 141 } else if (params.state == ViewMsg_AudioStreamState_Params::kPaused) { | |
| 142 OnStreamPaused(msg.routing_id(), stream_id); | |
| 143 } else if (params.state == ViewMsg_AudioStreamState_Params::kError) { | |
| 144 OnStreamError(msg.routing_id(), stream_id); | |
| 145 } else { | |
| 146 FAIL() << "Unknown stream state"; | |
| 147 } | |
| 148 } | |
| 149 | |
| 150 void OnStreamVolume(const IPC::Message& msg, int stream_id, double volume) { | |
| 151 OnStreamVolume(msg.routing_id(), stream_id, volume); | |
| 152 } | |
| 153 | |
| 154 scoped_ptr<base::SharedMemory> shared_memory_; | |
| 155 scoped_ptr<base::SyncSocket> sync_socket_; | |
| 156 uint32 shared_memory_length_; | |
| 157 | |
| 158 DISALLOW_COPY_AND_ASSIGN(MockAudioRendererHost); | |
| 159 }; | |
| 160 | |
| 161 ACTION_P(QuitMessageLoop, message_loop) { | |
| 162 message_loop->PostTask(FROM_HERE, new MessageLoop::QuitTask()); | |
| 163 } | |
| 164 | |
| 165 class AudioRendererHostTest : public testing::Test { | |
| 166 public: | |
| 167 AudioRendererHostTest() | |
| 168 : mock_stream_(true) { | |
| 169 } | |
| 170 | |
| 171 protected: | |
| 172 virtual void SetUp() { | |
| 173 // Create a message loop so AudioRendererHost can use it. | |
| 174 message_loop_.reset(new MessageLoop(MessageLoop::TYPE_IO)); | |
| 175 io_thread_.reset(new BrowserThread(BrowserThread::IO, message_loop_.get())); | |
| 176 host_ = new MockAudioRendererHost(); | |
| 177 | |
| 178 // Simulate IPC channel connected. | |
| 179 host_->OnChannelConnected(base::GetCurrentProcId()); | |
| 180 } | |
| 181 | |
| 182 virtual void TearDown() { | |
| 183 // Simulate closing the IPC channel. | |
| 184 host_->OnChannelClosing(); | |
| 185 | |
| 186 // Release the reference to the mock object. The object will be destructed | |
| 187 // on message_loop_. | |
| 188 host_ = NULL; | |
| 189 | |
| 190 // We need to continue running message_loop_ to complete all destructions. | |
| 191 SyncWithAudioThread(); | |
| 192 | |
| 193 io_thread_.reset(); | |
| 194 } | |
| 195 | |
| 196 void Create() { | |
| 197 InSequence s; | |
| 198 // 1. We will first receive a OnStreamCreated() signal. | |
| 199 EXPECT_CALL(*host_, | |
| 200 OnStreamCreated(kRouteId, kStreamId, _)); | |
| 201 | |
| 202 // 2. First packet request will arrive. | |
| 203 EXPECT_CALL(*host_, OnRequestPacket(kRouteId, kStreamId, _)) | |
| 204 .WillOnce(QuitMessageLoop(message_loop_.get())); | |
| 205 | |
| 206 IPC::Message msg; | |
| 207 msg.set_routing_id(kRouteId); | |
| 208 | |
| 209 ViewHostMsg_Audio_CreateStream_Params params; | |
| 210 if (mock_stream_) | |
| 211 params.params.format = AudioParameters::AUDIO_MOCK; | |
| 212 else | |
| 213 params.params.format = AudioParameters::AUDIO_PCM_LINEAR; | |
| 214 params.params.channels = 2; | |
| 215 params.params.sample_rate = AudioParameters::kAudioCDSampleRate; | |
| 216 params.params.bits_per_sample = 16; | |
| 217 params.params.samples_per_packet = 0; | |
| 218 | |
| 219 // Send a create stream message to the audio output stream and wait until | |
| 220 // we receive the created message. | |
| 221 host_->OnCreateStream(msg, kStreamId, params, false); | |
| 222 message_loop_->Run(); | |
| 223 } | |
| 224 | |
| 225 void CreateLowLatency() { | |
| 226 InSequence s; | |
| 227 // We will first receive a OnLowLatencyStreamCreated() signal. | |
| 228 EXPECT_CALL(*host_, | |
| 229 OnLowLatencyStreamCreated(kRouteId, kStreamId, _)) | |
| 230 .WillOnce(QuitMessageLoop(message_loop_.get())); | |
| 231 | |
| 232 IPC::Message msg; | |
| 233 msg.set_routing_id(kRouteId); | |
| 234 | |
| 235 ViewHostMsg_Audio_CreateStream_Params params; | |
| 236 if (mock_stream_) | |
| 237 params.params.format = AudioParameters::AUDIO_MOCK; | |
| 238 else | |
| 239 params.params.format = AudioParameters::AUDIO_PCM_LINEAR; | |
| 240 params.params.channels = 2; | |
| 241 params.params.sample_rate = AudioParameters::kAudioCDSampleRate; | |
| 242 params.params.bits_per_sample = 16; | |
| 243 params.params.samples_per_packet = 0; | |
| 244 | |
| 245 // Send a create stream message to the audio output stream and wait until | |
| 246 // we receive the created message. | |
| 247 host_->OnCreateStream(msg, kStreamId, params, true); | |
| 248 message_loop_->Run(); | |
| 249 } | |
| 250 | |
| 251 void Close() { | |
| 252 // Send a message to AudioRendererHost to tell it we want to close the | |
| 253 // stream. | |
| 254 IPC::Message msg; | |
| 255 msg.set_routing_id(kRouteId); | |
| 256 host_->OnCloseStream(msg, kStreamId); | |
| 257 message_loop_->RunAllPending(); | |
| 258 } | |
| 259 | |
| 260 void Play() { | |
| 261 EXPECT_CALL(*host_, OnStreamPlaying(kRouteId, kStreamId)) | |
| 262 .WillOnce(QuitMessageLoop(message_loop_.get())); | |
| 263 | |
| 264 IPC::Message msg; | |
| 265 msg.set_routing_id(kRouteId); | |
| 266 host_->OnPlayStream(msg, kStreamId); | |
| 267 message_loop_->Run(); | |
| 268 } | |
| 269 | |
| 270 void Pause() { | |
| 271 EXPECT_CALL(*host_, OnStreamPaused(kRouteId, kStreamId)) | |
| 272 .WillOnce(QuitMessageLoop(message_loop_.get())); | |
| 273 | |
| 274 IPC::Message msg; | |
| 275 msg.set_routing_id(kRouteId); | |
| 276 host_->OnPauseStream(msg, kStreamId); | |
| 277 message_loop_->Run(); | |
| 278 } | |
| 279 | |
| 280 void SetVolume(double volume) { | |
| 281 IPC::Message msg; | |
| 282 msg.set_routing_id(kRouteId); | |
| 283 host_->OnSetVolume(msg, kStreamId, volume); | |
| 284 message_loop_->RunAllPending(); | |
| 285 } | |
| 286 | |
| 287 void NotifyPacketReady() { | |
| 288 EXPECT_CALL(*host_, OnRequestPacket(kRouteId, kStreamId, _)) | |
| 289 .WillOnce(QuitMessageLoop(message_loop_.get())); | |
| 290 | |
| 291 IPC::Message msg; | |
| 292 msg.set_routing_id(kRouteId); | |
| 293 memset(host_->shared_memory()->memory(), 0, host_->shared_memory_length()); | |
| 294 host_->OnNotifyPacketReady(msg, kStreamId, | |
| 295 host_->shared_memory_length()); | |
| 296 message_loop_->Run(); | |
| 297 } | |
| 298 | |
| 299 void SimulateError() { | |
| 300 // Find the first AudioOutputController in the AudioRendererHost. | |
| 301 CHECK(host_->audio_entries_.size()) | |
| 302 << "Calls Create() before calling this method"; | |
| 303 media::AudioOutputController* controller = | |
| 304 host_->audio_entries_.begin()->second->controller; | |
| 305 CHECK(controller) << "AudioOutputController not found"; | |
| 306 | |
| 307 // Expect an error signal sent through IPC. | |
| 308 EXPECT_CALL(*host_, OnStreamError(kRouteId, kStreamId)); | |
| 309 | |
| 310 // Simulate an error sent from the audio device. | |
| 311 host_->OnError(controller, 0); | |
| 312 SyncWithAudioThread(); | |
| 313 | |
| 314 // Expect the audio stream record is removed. | |
| 315 EXPECT_EQ(0u, host_->audio_entries_.size()); | |
| 316 } | |
| 317 | |
| 318 // Called on the audio thread. | |
| 319 static void PostQuitMessageLoop(MessageLoop* message_loop) { | |
| 320 message_loop->PostTask(FROM_HERE, new MessageLoop::QuitTask()); | |
| 321 } | |
| 322 | |
| 323 // Called on the main thread. | |
| 324 static void PostQuitOnAudioThread(MessageLoop* message_loop) { | |
| 325 AudioManager::GetAudioManager()->GetMessageLoop()->PostTask( | |
| 326 FROM_HERE, NewRunnableFunction(&PostQuitMessageLoop, message_loop)); | |
| 327 } | |
| 328 | |
| 329 // SyncWithAudioThread() waits until all pending tasks on the audio thread | |
| 330 // are executed while also processing pending task in message_loop_ on the | |
| 331 // current thread. It is used to synchronize with the audio thread when we are | |
| 332 // closing an audio stream. | |
| 333 void SyncWithAudioThread() { | |
| 334 message_loop_->PostTask( | |
| 335 FROM_HERE, NewRunnableFunction(&PostQuitOnAudioThread, | |
| 336 message_loop_.get())); | |
| 337 message_loop_->Run(); | |
| 338 } | |
| 339 | |
| 340 MessageLoop* message_loop() { return message_loop_.get(); } | |
| 341 MockAudioRendererHost* host() { return host_; } | |
| 342 void EnableRealDevice() { mock_stream_ = false; } | |
| 343 | |
| 344 private: | |
| 345 bool mock_stream_; | |
| 346 scoped_refptr<MockAudioRendererHost> host_; | |
| 347 scoped_ptr<MessageLoop> message_loop_; | |
| 348 scoped_ptr<BrowserThread> io_thread_; | |
| 349 | |
| 350 DISALLOW_COPY_AND_ASSIGN(AudioRendererHostTest); | |
| 351 }; | |
| 352 | |
| 353 TEST_F(AudioRendererHostTest, CreateAndClose) { | |
| 354 if (!IsRunningHeadless()) | |
| 355 EnableRealDevice(); | |
| 356 | |
| 357 Create(); | |
| 358 Close(); | |
| 359 } | |
| 360 | |
| 361 TEST_F(AudioRendererHostTest, CreatePlayAndClose) { | |
| 362 if (!IsRunningHeadless()) | |
| 363 EnableRealDevice(); | |
| 364 | |
| 365 Create(); | |
| 366 Play(); | |
| 367 Close(); | |
| 368 } | |
| 369 | |
| 370 TEST_F(AudioRendererHostTest, CreatePlayPauseAndClose) { | |
| 371 if (!IsRunningHeadless()) | |
| 372 EnableRealDevice(); | |
| 373 | |
| 374 Create(); | |
| 375 Play(); | |
| 376 Pause(); | |
| 377 Close(); | |
| 378 } | |
| 379 | |
| 380 TEST_F(AudioRendererHostTest, SetVolume) { | |
| 381 if (!IsRunningHeadless()) | |
| 382 EnableRealDevice(); | |
| 383 | |
| 384 Create(); | |
| 385 SetVolume(0.5); | |
| 386 Play(); | |
| 387 Pause(); | |
| 388 Close(); | |
| 389 | |
| 390 // Expect the volume is set. | |
| 391 if (IsRunningHeadless()) { | |
| 392 EXPECT_EQ(0.5, FakeAudioOutputStream::GetLastFakeStream()->volume()); | |
| 393 } | |
| 394 } | |
| 395 | |
| 396 // Simulate the case where a stream is not properly closed. | |
| 397 TEST_F(AudioRendererHostTest, CreateAndShutdown) { | |
| 398 if (!IsRunningHeadless()) | |
| 399 EnableRealDevice(); | |
| 400 | |
| 401 Create(); | |
| 402 } | |
| 403 | |
| 404 // Simulate the case where a stream is not properly closed. | |
| 405 TEST_F(AudioRendererHostTest, CreatePlayAndShutdown) { | |
| 406 if (!IsRunningHeadless()) | |
| 407 EnableRealDevice(); | |
| 408 | |
| 409 Create(); | |
| 410 Play(); | |
| 411 } | |
| 412 | |
| 413 // Simulate the case where a stream is not properly closed. | |
| 414 TEST_F(AudioRendererHostTest, CreatePlayPauseAndShutdown) { | |
| 415 if (!IsRunningHeadless()) | |
| 416 EnableRealDevice(); | |
| 417 | |
| 418 Create(); | |
| 419 Play(); | |
| 420 Pause(); | |
| 421 } | |
| 422 | |
| 423 TEST_F(AudioRendererHostTest, DataConversationMockStream) { | |
| 424 Create(); | |
| 425 | |
| 426 // Note that we only do notify three times because the buffer capacity is | |
| 427 // triple of one packet size. | |
| 428 NotifyPacketReady(); | |
| 429 NotifyPacketReady(); | |
| 430 NotifyPacketReady(); | |
| 431 Close(); | |
| 432 } | |
| 433 | |
| 434 TEST_F(AudioRendererHostTest, DataConversationRealStream) { | |
| 435 if (IsRunningHeadless()) | |
| 436 return; | |
| 437 EnableRealDevice(); | |
| 438 Create(); | |
| 439 Play(); | |
| 440 | |
| 441 // If this is a real audio device, the data conversation is not limited | |
| 442 // to the buffer capacity of AudioOutputController. So we do 5 exchanges | |
| 443 // before we close the device. | |
| 444 for (int i = 0; i < 5; ++i) { | |
| 445 NotifyPacketReady(); | |
| 446 } | |
| 447 Close(); | |
| 448 } | |
| 449 | |
| 450 TEST_F(AudioRendererHostTest, SimulateError) { | |
| 451 if (!IsRunningHeadless()) | |
| 452 EnableRealDevice(); | |
| 453 | |
| 454 Create(); | |
| 455 Play(); | |
| 456 SimulateError(); | |
| 457 } | |
| 458 | |
| 459 // Simulate the case when an error is generated on the browser process, | |
| 460 // the audio device is closed but the render process try to close the | |
| 461 // audio stream again. | |
| 462 TEST_F(AudioRendererHostTest, SimulateErrorAndClose) { | |
| 463 if (!IsRunningHeadless()) | |
| 464 EnableRealDevice(); | |
| 465 | |
| 466 Create(); | |
| 467 Play(); | |
| 468 SimulateError(); | |
| 469 Close(); | |
| 470 } | |
| 471 | |
| 472 TEST_F(AudioRendererHostTest, CreateLowLatencyAndClose) { | |
| 473 if (!IsRunningHeadless()) | |
| 474 EnableRealDevice(); | |
| 475 | |
| 476 CreateLowLatency(); | |
| 477 Close(); | |
| 478 } | |
| 479 | |
| 480 // Simulate the case where a stream is not properly closed. | |
| 481 TEST_F(AudioRendererHostTest, CreateLowLatencyAndShutdown) { | |
| 482 if (!IsRunningHeadless()) | |
| 483 EnableRealDevice(); | |
| 484 | |
| 485 CreateLowLatency(); | |
| 486 } | |
| 487 | |
| 488 // TODO(hclam): Add tests for data conversation in low latency mode. | |
| OLD | NEW |