|
OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2011 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/bind.h" | |
6 #include "base/message_loop.h" | |
7 #include "base/path_service.h" | |
8 #include "base/process_util.h" | |
9 #include "base/synchronization/waitable_event.h" | |
10 #include "base/test/test_timeouts.h" | |
11 #include "base/time.h" | |
12 | |
Paweł Hajdan Jr.
2011/11/02 09:29:15
nit: We're not doing gaps like this in the #includ
tommi (sloooow) - chröme
2011/11/02 10:59:23
Done.
| |
13 #include "content/browser/mock_resource_context.h" | |
14 #include "content/browser/renderer_host/media/audio_renderer_host.h" | |
15 #include "content/browser/renderer_host/media/mock_media_observer.h" | |
16 #include "content/common/child_process.h" | |
17 #include "content/common/child_thread.h" | |
18 #include "content/common/media/audio_messages.h" | |
19 #include "content/common/view_messages.h" | |
20 #include "content/public/common/content_paths.h" | |
21 #include "content/renderer/media/audio_renderer_impl.h" | |
22 #include "content/renderer/media/webrtc_audio_device_impl.h" | |
23 #include "content/renderer/mock_content_renderer_client.h" | |
24 #include "content/renderer/render_process.h" | |
25 #include "content/renderer/render_thread_impl.h" | |
26 #include "content/test/test_browser_thread.h" | |
27 | |
28 #include "ipc/ipc_channel.h" | |
29 | |
30 #include "media/audio/audio_util.h" | |
31 #include "media/base/data_buffer.h" | |
32 #include "media/base/mock_callback.h" | |
33 #include "media/base/mock_filter_host.h" | |
34 #include "media/base/mock_filters.h" | |
35 | |
36 #include "net/url_request/url_request_test_util.h" | |
37 | |
38 #include "testing/gmock/include/gmock/gmock.h" | |
39 #include "testing/gtest/include/gtest/gtest.h" | |
40 | |
41 #include "third_party/webrtc/voice_engine/main/interface/voe_audio_processing.h" | |
42 #include "third_party/webrtc/voice_engine/main/interface/voe_base.h" | |
43 #include "third_party/webrtc/voice_engine/main/interface/voe_file.h" | |
44 #include "third_party/webrtc/voice_engine/main/interface/voe_network.h" | |
45 | |
46 using testing::_; | |
47 using testing::InvokeWithoutArgs; | |
48 using testing::Return; | |
49 using testing::StrEq; | |
50 | |
51 namespace { | |
52 // This class is a mock of the child process singleton which is needed | |
53 // to be able to create a RenderThread object. | |
54 class MockRenderProcess : public RenderProcess { | |
55 public: | |
56 MockRenderProcess() {} | |
57 virtual ~MockRenderProcess() {} | |
58 | |
59 // RenderProcess implementation. | |
60 virtual skia::PlatformCanvas* GetDrawingCanvas(TransportDIB** memory, | |
61 const gfx::Rect& rect) { return NULL; } | |
62 virtual void ReleaseTransportDIB(TransportDIB* memory) {} | |
63 virtual bool UseInProcessPlugins() const { return false; } | |
64 virtual bool HasInitializedMediaLibrary() const { return false; } | |
65 | |
66 private: | |
67 DISALLOW_COPY_AND_ASSIGN(MockRenderProcess); | |
68 }; | |
69 | |
70 class WebRTCMockResourceContext : public content::ResourceContext { | |
71 public: | |
72 WebRTCMockResourceContext() {} | |
73 virtual ~WebRTCMockResourceContext() {} | |
74 virtual void EnsureInitialized() const OVERRIDE {} | |
75 }; | |
76 | |
77 // Convenience class for WebRTC interfaces. Fetches the wrapped interface | |
78 // in the constructor via WebRTC's GetInterface mechanism and then releases | |
79 // the reference in the destructor. | |
80 template<typename T> | |
81 class ScopedWebRTCPtr { | |
82 public: | |
83 template<typename Engine> | |
84 explicit ScopedWebRTCPtr(Engine* e) | |
85 : ptr_(T::GetInterface(e)) {} | |
86 explicit ScopedWebRTCPtr(T* p) : ptr_(p) {} | |
87 ~ScopedWebRTCPtr() { reset(); } | |
88 T* operator->() const { return ptr_; } | |
89 T* get() const { return ptr_; } | |
90 | |
91 // Releases the current pointer. | |
92 void reset() { | |
93 if (ptr_) { | |
94 ptr_->Release(); | |
95 ptr_ = NULL; | |
96 } | |
97 } | |
98 | |
99 bool valid() const { return ptr_ != NULL; } | |
100 | |
101 private: | |
102 T* ptr_; | |
103 }; | |
104 | |
105 // Wrapper to automatically calling T::Delete in the destructor. | |
106 // This is useful for some WebRTC objects that have their own Create/Delete | |
107 // methods and we can't use our our scoped_* classes. | |
108 template <typename T> | |
109 class AutoDelete { | |
110 public: | |
111 AutoDelete() : ptr_(NULL) {} | |
112 explicit AutoDelete(T* ptr) : ptr_(ptr) {} | |
113 ~AutoDelete() { reset(); } | |
114 | |
115 void reset() { | |
116 if (ptr_) { | |
117 T::Delete(ptr_); | |
118 ptr_ = NULL; | |
119 } | |
120 } | |
121 | |
122 T* operator->() { return ptr_; } | |
123 T* get() const { return ptr_; } | |
124 | |
125 bool valid() const { return ptr_ != NULL; } | |
126 | |
127 protected: | |
128 T* ptr_; | |
129 }; | |
130 | |
131 // This task signals an event owned by the task owner when run and quits. | |
132 class SignalTask : public Task { | |
Paweł Hajdan Jr.
2011/11/02 09:29:15
We already have a class like this (chrome/test/bas
tommi (sloooow) - chröme
2011/11/02 10:59:23
http://codereview.chromium.org/8438034/
| |
133 public: | |
134 explicit SignalTask(base::WaitableEvent* event) : event_(event) {} | |
135 virtual ~SignalTask() {} | |
136 virtual void Run() { event_->Signal(); } | |
137 private: | |
138 base::WaitableEvent* event_; | |
139 DISALLOW_COPY_AND_ASSIGN(SignalTask); | |
140 }; | |
141 | |
142 // Quits the current message loop when run and also sets a supplied bool | |
143 // to true. If the task never ran, the bool should remain false. | |
144 class QuitAndRaiseTask : public Task { | |
145 public: | |
146 explicit QuitAndRaiseTask(bool* was_run) : was_run_(was_run) { | |
147 *was_run = false; | |
148 } | |
149 | |
150 virtual void Run() { | |
151 *was_run_ = true; | |
152 MessageLoop::current()->Quit(); | |
153 } | |
154 protected: | |
155 bool* was_run_; | |
156 }; | |
157 | |
158 ACTION_P(QuitMessageLoop, loop_or_proxy) { | |
159 LOG(WARNING) << __FUNCTION__; | |
160 loop_or_proxy->PostTask(FROM_HERE, new MessageLoop::QuitTask()); | |
161 } | |
162 | |
163 } // end namespace | |
164 | |
165 // IPC::Channel::Listener implementation for the WebRTCAudioDeviceTest test | |
166 // fixture. Provides basic implementations for initialization tasks and | |
167 // forwards callbacks to audio ipc filters. | |
168 template <class T> | |
169 class WebRTCAudioDeviceTestChannelListener : public IPC::Channel::Listener { | |
170 public: | |
171 WebRTCAudioDeviceTestChannelListener() {} | |
172 ~WebRTCAudioDeviceTestChannelListener() {} | |
173 | |
174 void OnGetHardwareSampleRate(double* sample_rate) { | |
175 *sample_rate = media::GetAudioHardwareSampleRate(); | |
176 } | |
177 | |
178 void OnGetHardwareInputSampleRate(double* sample_rate) { | |
179 *sample_rate = media::GetAudioInputHardwareSampleRate(); | |
180 } | |
181 | |
182 bool Send(IPC::Message* message) { | |
183 return channel_->Send(message); | |
184 } | |
185 | |
186 // IPC::Channel::Listener implementation. | |
187 virtual bool OnMessageReceived(const IPC::Message& message) { | |
188 T* me = static_cast<T*>(this); | |
189 if (me->render_thread()) { | |
190 IPC::ChannelProxy::MessageFilter* filter = | |
191 me->render_thread()->audio_input_message_filter(); | |
192 if (filter->OnMessageReceived(message)) | |
193 return true; | |
194 | |
195 filter = me->render_thread()->audio_message_filter(); | |
196 if (filter->OnMessageReceived(message)) | |
197 return true; | |
198 } | |
199 | |
200 if (audio_render_host_.get()) { | |
201 bool message_was_ok = false; | |
202 if (audio_render_host_->OnMessageReceived(message, &message_was_ok)) | |
203 return true; | |
204 } | |
205 | |
206 bool handled = true; | |
207 IPC_BEGIN_MESSAGE_MAP(WebRTCAudioDeviceTest, message) | |
Paweł Hajdan Jr.
2011/11/02 09:29:15
Please use MESSAGE_MAP_EX to avoid an implicit DCH
tommi (sloooow) - chröme
2011/11/02 10:59:23
Done.
| |
208 IPC_MESSAGE_HANDLER(ViewHostMsg_GetHardwareSampleRate, | |
209 OnGetHardwareSampleRate) | |
210 IPC_MESSAGE_HANDLER(ViewHostMsg_GetHardwareInputSampleRate, | |
211 OnGetHardwareInputSampleRate) | |
212 IPC_MESSAGE_UNHANDLED(handled = false) | |
213 IPC_END_MESSAGE_MAP() | |
214 | |
215 if (!handled) { | |
216 DLOG(WARNING) << "Unhandled IPC message"; | |
Paweł Hajdan Jr.
2011/11/02 09:29:15
Don't you want to make the test fail in that case?
tommi (sloooow) - chröme
2011/11/02 10:59:23
I added this comment:
if (!handled) {
/
| |
217 } | |
218 | |
219 return true; | |
220 } | |
221 | |
222 protected: | |
223 void CreateChannel(const char* name, | |
224 content::ResourceContext* resource_context) { | |
225 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
226 audio_render_host_ = new AudioRendererHost(resource_context); | |
227 audio_render_host_->OnChannelConnected(base::GetCurrentProcId()); | |
228 | |
229 channel_.reset(new IPC::Channel(name, IPC::Channel::MODE_SERVER, this)); | |
230 ASSERT_TRUE(channel_->Connect()); | |
231 | |
232 audio_render_host_->OnFilterAdded(channel_.get()); | |
233 } | |
234 | |
235 void DestroyChannel() { | |
236 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
237 channel_.reset(); | |
238 audio_render_host_ = NULL; | |
239 } | |
240 | |
241 scoped_ptr<IPC::Channel> channel_; | |
242 scoped_refptr<AudioRendererHost> audio_render_host_; | |
243 }; | |
244 | |
245 class WebRTCAudioDeviceTest | |
246 : public ::testing::Test, | |
247 public WebRTCAudioDeviceTestChannelListener<WebRTCAudioDeviceTest> { | |
248 public: | |
249 class SetupTask : public base::RefCountedThreadSafe<SetupTask> { | |
250 public: | |
251 explicit SetupTask(WebRTCAudioDeviceTest* test) : test_(test) {} | |
252 void InitializeIOThread(const char* thread_name) { | |
253 test_->InitializeIOThread(thread_name); | |
254 } | |
255 void UninitializeIOThread() { test_->UninitializeIOThread(); } | |
256 protected: | |
257 WebRTCAudioDeviceTest* test_; | |
258 }; | |
259 | |
260 WebRTCAudioDeviceTest() : render_thread_(NULL), event_(false, false) {} | |
261 virtual ~WebRTCAudioDeviceTest() {} | |
262 | |
263 static void SetUpTestCase() { | |
Paweł Hajdan Jr.
2011/11/02 09:29:15
Shouldn't you have a corresponding TearDownTestCas
tommi (sloooow) - chröme
2011/11/02 10:59:23
This fixture is based in parts on the AudioRendere
| |
264 // Set low latency mode, as it soon would be on by default. | |
265 if (AudioRendererImpl::latency_type() == | |
266 AudioRendererImpl::kUninitializedLatency) { | |
267 AudioRendererImpl::set_latency_type(AudioRendererImpl::kLowLatency); | |
268 } | |
269 DCHECK_EQ(AudioRendererImpl::kLowLatency, | |
270 AudioRendererImpl::latency_type()); | |
271 } | |
272 | |
273 virtual void SetUp() { | |
274 // This part sets up a RenderThread environment to ensure that | |
275 // RenderThread::current() (<=> TLS pointer) is valid. | |
276 // Main parts are inspired by the RenderViewFakeResourcesTest. | |
277 // Note that, the IPC part is not utilized in this test. | |
278 content::GetContentClient()->set_renderer(&mock_content_renderer_client_); | |
Paweł Hajdan Jr.
2011/11/02 09:29:15
Is this undone in TearDown? Please use a scoped ob
tommi (sloooow) - chröme
2011/11/02 10:59:23
This is another thing that is borrowed from AudioR
| |
279 mock_process_.reset(new MockRenderProcess); | |
280 ui_thread_.reset(new content::TestBrowserThread(BrowserThread::UI, | |
281 MessageLoop::current())); | |
282 | |
283 // Construct the resource context on the UI thread. | |
284 resource_context_.reset(new WebRTCMockResourceContext()); | |
285 | |
286 static const char kThreadName[] = "RenderThread"; | |
287 ChildProcess::current()->io_message_loop()->PostTask( | |
288 FROM_HERE, | |
289 base::Bind(&SetupTask::InitializeIOThread, new SetupTask(this), | |
290 kThreadName)); | |
291 WaitForIOThreadCompletion(); | |
292 | |
293 render_thread_ = new RenderThreadImpl(kThreadName); | |
294 mock_process_->set_main_thread(render_thread_); | |
295 } | |
296 | |
297 virtual void TearDown() { | |
298 ChildProcess::current()->io_message_loop()->PostTask( | |
299 FROM_HERE, | |
300 base::Bind(&SetupTask::UninitializeIOThread, new SetupTask(this))); | |
301 WaitForIOThreadCompletion(); | |
302 mock_process_.reset(); | |
303 } | |
304 | |
305 RenderThreadImpl* render_thread() const { | |
306 return render_thread_; | |
307 } | |
308 | |
309 protected: | |
310 void InitializeIOThread(const char* thread_name) { | |
311 // Set the current thread as the IO thread. | |
312 io_thread_.reset(new content::TestBrowserThread(BrowserThread::IO, | |
313 MessageLoop::current())); | |
314 test_request_context_ = new TestURLRequestContext(); | |
315 resource_context_->set_request_context(test_request_context_.get()); | |
316 media_observer_.reset(new MockMediaObserver()); | |
317 resource_context_->set_media_observer(media_observer_.get()); | |
318 | |
319 CreateChannel(thread_name, resource_context_.get()); | |
320 } | |
321 | |
322 void UninitializeIOThread() { | |
323 DestroyChannel(); | |
324 resource_context_.reset(); | |
325 test_request_context_ = NULL; | |
326 } | |
327 | |
328 // Posts a final task to the IO message loop and waits for completion. | |
329 void WaitForIOThreadCompletion() { | |
330 ChildProcess::current()->io_message_loop()->PostTask( | |
331 FROM_HERE, new SignalTask(&event_)); | |
332 EXPECT_TRUE(event_.TimedWait( | |
333 base::TimeDelta::FromMilliseconds(TestTimeouts::action_timeout_ms()))); | |
334 } | |
335 | |
336 // Convenience getter for gmock. | |
337 MockMediaObserver& media_observer() const { | |
338 return *media_observer_.get(); | |
339 } | |
340 | |
341 std::string GetTestDataPath(const FilePath::StringType& file_name) { | |
342 FilePath path; | |
343 PathService::Get(content::DIR_TEST_DATA, &path); | |
Paweł Hajdan Jr.
2011/11/02 09:29:15
Check the return value.
tommi (sloooow) - chröme
2011/11/02 10:59:23
Done.
| |
344 path = path.Append(file_name); | |
345 #ifdef OS_WIN | |
346 return WideToUTF8(path.value()); | |
347 #else | |
348 return path.value(); | |
349 #endif | |
350 } | |
351 | |
352 // Actual implementation of the short and long PlayFile tests. | |
353 // If the |duration| parameter is 0, then the function determines the length | |
354 // of the audio file and plays the entire file. Otherwise, |duration| | |
355 // specifies the number of milliseconds to allow the file to play. | |
356 void PlayLocalFile(int duration); | |
357 | |
358 MessageLoopForUI message_loop_; | |
359 content::MockContentRendererClient mock_content_renderer_client_; | |
360 RenderThreadImpl* render_thread_; // owned by mock_process_ | |
361 scoped_ptr<MockRenderProcess> mock_process_; | |
362 media::MockFilterHost host_; | |
363 base::WaitableEvent event_; | |
364 scoped_ptr<MockMediaObserver> media_observer_; | |
365 scoped_ptr<content::ResourceContext> resource_context_; | |
366 scoped_refptr<net::URLRequestContext> test_request_context_; | |
367 | |
368 // Initialized on the main test thread that we mark as the UI thread. | |
369 scoped_ptr<content::TestBrowserThread> ui_thread_; | |
370 // Initialized on our IO thread to satisfy BrowserThread::IO checks. | |
371 scoped_ptr<content::TestBrowserThread> io_thread_; | |
372 | |
373 private: | |
374 DISALLOW_COPY_AND_ASSIGN(WebRTCAudioDeviceTest); | |
Paweł Hajdan Jr.
2011/11/02 09:29:15
nit: No need for that in a test fixture.
tommi (sloooow) - chröme
2011/11/02 10:59:23
Done.
| |
375 }; | |
376 | |
377 // A very basic implementation of webrtc::Transport that acts as a transport | |
378 // but just forwards all calls to a local webrtc::VoENetwork implementation. | |
379 // Ownership of the VoENetwork object lies outside the class. | |
380 class TransportImpl : public webrtc::Transport { | |
381 public: | |
382 explicit TransportImpl(webrtc::VoENetwork* network) : network_(network) {} | |
383 ~TransportImpl() {} | |
384 | |
385 virtual int SendPacket(int channel, const void* data, int len) { | |
386 DVLOG(1) << __FUNCTION__; | |
387 return network_->ReceivedRTPPacket(channel, data, len); | |
388 } | |
389 | |
390 virtual int SendRTCPPacket(int channel, const void* data, int len) { | |
391 DVLOG(1) << __FUNCTION__; | |
392 return network_->ReceivedRTCPPacket(channel, data, len); | |
393 } | |
394 | |
395 private: | |
396 webrtc::VoENetwork* network_; | |
397 }; | |
398 | |
399 // Basic test that instantiates and initializes an instance of | |
400 // WebRtcAudioDeviceImpl. | |
401 TEST_F(WebRTCAudioDeviceTest, Construct) { | |
402 scoped_refptr<WebRtcAudioDeviceImpl> audio_device( | |
403 new WebRtcAudioDeviceImpl()); | |
404 audio_device->SetSessionId(1); | |
405 | |
406 AutoDelete<webrtc::VoiceEngine> engine(webrtc::VoiceEngine::Create()); | |
407 | |
408 ScopedWebRTCPtr<webrtc::VoEBase> base(engine.get()); | |
409 int err = base->Init(audio_device); | |
410 EXPECT_EQ(0, err); | |
411 } | |
412 | |
413 void WebRTCAudioDeviceTest::PlayLocalFile(int duration) { | |
414 EXPECT_GE(duration, 0); | |
415 EXPECT_CALL(media_observer(), | |
416 OnSetAudioStreamStatus(_, 1, StrEq("created"))).Times(1); | |
417 | |
418 EXPECT_CALL(media_observer(), | |
419 OnSetAudioStreamPlaying(_, 1, true)).Times(1); | |
420 | |
421 // When the "closed" event is triggered, we end the test. | |
422 EXPECT_CALL(media_observer(), | |
423 OnSetAudioStreamStatus(_, 1, StrEq("closed"))) | |
424 .WillOnce(QuitMessageLoop(message_loop_.message_loop_proxy())); | |
425 | |
426 EXPECT_CALL(media_observer(), | |
427 OnDeleteAudioStream(_, 1)).Times(1); | |
428 | |
429 scoped_refptr<WebRtcAudioDeviceImpl> audio_device( | |
430 new WebRtcAudioDeviceImpl()); | |
431 audio_device->SetSessionId(1); | |
432 | |
433 AutoDelete<webrtc::VoiceEngine> engine(webrtc::VoiceEngine::Create()); | |
434 | |
435 ScopedWebRTCPtr<webrtc::VoEBase> base(engine.get()); | |
436 int err = base->Init(audio_device); | |
437 EXPECT_EQ(0, err); | |
438 if (err == 0) { | |
439 ScopedWebRTCPtr<webrtc::VoEAudioProcessing> audio_processing(engine.get()); | |
440 EXPECT_EQ(0, audio_processing->SetAgcStatus(true, | |
441 webrtc::kAgcAdaptiveDigital)); | |
442 | |
443 int ch = base->CreateChannel(); | |
444 EXPECT_NE(-1, ch); | |
445 | |
446 ScopedWebRTCPtr<webrtc::VoENetwork> network(engine.get()); | |
447 scoped_ptr<TransportImpl> transport(new TransportImpl(network.get())); | |
448 EXPECT_EQ(0, network->RegisterExternalTransport(ch, *transport.get())); | |
449 EXPECT_EQ(0, base->StartReceive(ch)); | |
450 EXPECT_EQ(0, base->StartPlayout(ch)); | |
451 EXPECT_EQ(0, base->StartSend(ch)); | |
452 | |
453 std::string file_path( | |
454 GetTestDataPath(FILE_PATH_LITERAL("speechmusic_mono_16kHz.pcm"))); | |
455 | |
456 ScopedWebRTCPtr<webrtc::VoEFile> file(engine.get()); | |
457 if (duration == 0) { | |
458 EXPECT_EQ(0, file->GetFileDuration(file_path.c_str(), duration, | |
459 webrtc::kFileFormatPcm16kHzFile)); | |
460 EXPECT_NE(0, duration); | |
461 } | |
462 | |
463 EXPECT_EQ(0, file->StartPlayingFileLocally(ch, file_path.c_str(), false, | |
464 webrtc::kFileFormatPcm16kHzFile)); | |
465 | |
466 message_loop_.PostDelayedTask(FROM_HERE, | |
467 new MessageLoop::QuitTask(), duration); | |
468 message_loop_.Run(); | |
469 | |
470 EXPECT_EQ(0, network->DeRegisterExternalTransport(ch)); | |
471 } | |
472 } | |
473 | |
474 // Plays a local file. This test usually takes just under a minute to run | |
475 // and requires an audio card to work, so disabled by default. | |
476 TEST_F(WebRTCAudioDeviceTest, DISABLED_PlayLocalFileLong) { | |
477 PlayLocalFile(0); | |
478 } | |
479 | |
480 TEST_F(WebRTCAudioDeviceTest, PlayLocalFile) { | |
481 PlayLocalFile(TestTimeouts::action_timeout_ms()); | |
482 } | |
OLD | NEW |