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 |