Index: content/renderer/media/webrtc_audio_device_unittest.cc |
=================================================================== |
--- content/renderer/media/webrtc_audio_device_unittest.cc (revision 0) |
+++ content/renderer/media/webrtc_audio_device_unittest.cc (working copy) |
@@ -0,0 +1,482 @@ |
+// Copyright (c) 2011 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "base/bind.h" |
+#include "base/message_loop.h" |
+#include "base/path_service.h" |
+#include "base/process_util.h" |
+#include "base/synchronization/waitable_event.h" |
+#include "base/test/test_timeouts.h" |
+#include "base/time.h" |
+ |
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.
|
+#include "content/browser/mock_resource_context.h" |
+#include "content/browser/renderer_host/media/audio_renderer_host.h" |
+#include "content/browser/renderer_host/media/mock_media_observer.h" |
+#include "content/common/child_process.h" |
+#include "content/common/child_thread.h" |
+#include "content/common/media/audio_messages.h" |
+#include "content/common/view_messages.h" |
+#include "content/public/common/content_paths.h" |
+#include "content/renderer/media/audio_renderer_impl.h" |
+#include "content/renderer/media/webrtc_audio_device_impl.h" |
+#include "content/renderer/mock_content_renderer_client.h" |
+#include "content/renderer/render_process.h" |
+#include "content/renderer/render_thread_impl.h" |
+#include "content/test/test_browser_thread.h" |
+ |
+#include "ipc/ipc_channel.h" |
+ |
+#include "media/audio/audio_util.h" |
+#include "media/base/data_buffer.h" |
+#include "media/base/mock_callback.h" |
+#include "media/base/mock_filter_host.h" |
+#include "media/base/mock_filters.h" |
+ |
+#include "net/url_request/url_request_test_util.h" |
+ |
+#include "testing/gmock/include/gmock/gmock.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+#include "third_party/webrtc/voice_engine/main/interface/voe_audio_processing.h" |
+#include "third_party/webrtc/voice_engine/main/interface/voe_base.h" |
+#include "third_party/webrtc/voice_engine/main/interface/voe_file.h" |
+#include "third_party/webrtc/voice_engine/main/interface/voe_network.h" |
+ |
+using testing::_; |
+using testing::InvokeWithoutArgs; |
+using testing::Return; |
+using testing::StrEq; |
+ |
+namespace { |
+// This class is a mock of the child process singleton which is needed |
+// to be able to create a RenderThread object. |
+class MockRenderProcess : public RenderProcess { |
+ public: |
+ MockRenderProcess() {} |
+ virtual ~MockRenderProcess() {} |
+ |
+ // RenderProcess implementation. |
+ virtual skia::PlatformCanvas* GetDrawingCanvas(TransportDIB** memory, |
+ const gfx::Rect& rect) { return NULL; } |
+ virtual void ReleaseTransportDIB(TransportDIB* memory) {} |
+ virtual bool UseInProcessPlugins() const { return false; } |
+ virtual bool HasInitializedMediaLibrary() const { return false; } |
+ |
+ private: |
+ DISALLOW_COPY_AND_ASSIGN(MockRenderProcess); |
+}; |
+ |
+class WebRTCMockResourceContext : public content::ResourceContext { |
+ public: |
+ WebRTCMockResourceContext() {} |
+ virtual ~WebRTCMockResourceContext() {} |
+ virtual void EnsureInitialized() const OVERRIDE {} |
+}; |
+ |
+// Convenience class for WebRTC interfaces. Fetches the wrapped interface |
+// in the constructor via WebRTC's GetInterface mechanism and then releases |
+// the reference in the destructor. |
+template<typename T> |
+class ScopedWebRTCPtr { |
+ public: |
+ template<typename Engine> |
+ explicit ScopedWebRTCPtr(Engine* e) |
+ : ptr_(T::GetInterface(e)) {} |
+ explicit ScopedWebRTCPtr(T* p) : ptr_(p) {} |
+ ~ScopedWebRTCPtr() { reset(); } |
+ T* operator->() const { return ptr_; } |
+ T* get() const { return ptr_; } |
+ |
+ // Releases the current pointer. |
+ void reset() { |
+ if (ptr_) { |
+ ptr_->Release(); |
+ ptr_ = NULL; |
+ } |
+ } |
+ |
+ bool valid() const { return ptr_ != NULL; } |
+ |
+ private: |
+ T* ptr_; |
+}; |
+ |
+// Wrapper to automatically calling T::Delete in the destructor. |
+// This is useful for some WebRTC objects that have their own Create/Delete |
+// methods and we can't use our our scoped_* classes. |
+template <typename T> |
+class AutoDelete { |
+ public: |
+ AutoDelete() : ptr_(NULL) {} |
+ explicit AutoDelete(T* ptr) : ptr_(ptr) {} |
+ ~AutoDelete() { reset(); } |
+ |
+ void reset() { |
+ if (ptr_) { |
+ T::Delete(ptr_); |
+ ptr_ = NULL; |
+ } |
+ } |
+ |
+ T* operator->() { return ptr_; } |
+ T* get() const { return ptr_; } |
+ |
+ bool valid() const { return ptr_ != NULL; } |
+ |
+ protected: |
+ T* ptr_; |
+}; |
+ |
+// This task signals an event owned by the task owner when run and quits. |
+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/
|
+ public: |
+ explicit SignalTask(base::WaitableEvent* event) : event_(event) {} |
+ virtual ~SignalTask() {} |
+ virtual void Run() { event_->Signal(); } |
+ private: |
+ base::WaitableEvent* event_; |
+ DISALLOW_COPY_AND_ASSIGN(SignalTask); |
+}; |
+ |
+// Quits the current message loop when run and also sets a supplied bool |
+// to true. If the task never ran, the bool should remain false. |
+class QuitAndRaiseTask : public Task { |
+ public: |
+ explicit QuitAndRaiseTask(bool* was_run) : was_run_(was_run) { |
+ *was_run = false; |
+ } |
+ |
+ virtual void Run() { |
+ *was_run_ = true; |
+ MessageLoop::current()->Quit(); |
+ } |
+ protected: |
+ bool* was_run_; |
+}; |
+ |
+ACTION_P(QuitMessageLoop, loop_or_proxy) { |
+ LOG(WARNING) << __FUNCTION__; |
+ loop_or_proxy->PostTask(FROM_HERE, new MessageLoop::QuitTask()); |
+} |
+ |
+} // end namespace |
+ |
+// IPC::Channel::Listener implementation for the WebRTCAudioDeviceTest test |
+// fixture. Provides basic implementations for initialization tasks and |
+// forwards callbacks to audio ipc filters. |
+template <class T> |
+class WebRTCAudioDeviceTestChannelListener : public IPC::Channel::Listener { |
+ public: |
+ WebRTCAudioDeviceTestChannelListener() {} |
+ ~WebRTCAudioDeviceTestChannelListener() {} |
+ |
+ void OnGetHardwareSampleRate(double* sample_rate) { |
+ *sample_rate = media::GetAudioHardwareSampleRate(); |
+ } |
+ |
+ void OnGetHardwareInputSampleRate(double* sample_rate) { |
+ *sample_rate = media::GetAudioInputHardwareSampleRate(); |
+ } |
+ |
+ bool Send(IPC::Message* message) { |
+ return channel_->Send(message); |
+ } |
+ |
+ // IPC::Channel::Listener implementation. |
+ virtual bool OnMessageReceived(const IPC::Message& message) { |
+ T* me = static_cast<T*>(this); |
+ if (me->render_thread()) { |
+ IPC::ChannelProxy::MessageFilter* filter = |
+ me->render_thread()->audio_input_message_filter(); |
+ if (filter->OnMessageReceived(message)) |
+ return true; |
+ |
+ filter = me->render_thread()->audio_message_filter(); |
+ if (filter->OnMessageReceived(message)) |
+ return true; |
+ } |
+ |
+ if (audio_render_host_.get()) { |
+ bool message_was_ok = false; |
+ if (audio_render_host_->OnMessageReceived(message, &message_was_ok)) |
+ return true; |
+ } |
+ |
+ bool handled = true; |
+ 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.
|
+ IPC_MESSAGE_HANDLER(ViewHostMsg_GetHardwareSampleRate, |
+ OnGetHardwareSampleRate) |
+ IPC_MESSAGE_HANDLER(ViewHostMsg_GetHardwareInputSampleRate, |
+ OnGetHardwareInputSampleRate) |
+ IPC_MESSAGE_UNHANDLED(handled = false) |
+ IPC_END_MESSAGE_MAP() |
+ |
+ if (!handled) { |
+ 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) {
/
|
+ } |
+ |
+ return true; |
+ } |
+ |
+ protected: |
+ void CreateChannel(const char* name, |
+ content::ResourceContext* resource_context) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
+ audio_render_host_ = new AudioRendererHost(resource_context); |
+ audio_render_host_->OnChannelConnected(base::GetCurrentProcId()); |
+ |
+ channel_.reset(new IPC::Channel(name, IPC::Channel::MODE_SERVER, this)); |
+ ASSERT_TRUE(channel_->Connect()); |
+ |
+ audio_render_host_->OnFilterAdded(channel_.get()); |
+ } |
+ |
+ void DestroyChannel() { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
+ channel_.reset(); |
+ audio_render_host_ = NULL; |
+ } |
+ |
+ scoped_ptr<IPC::Channel> channel_; |
+ scoped_refptr<AudioRendererHost> audio_render_host_; |
+}; |
+ |
+class WebRTCAudioDeviceTest |
+ : public ::testing::Test, |
+ public WebRTCAudioDeviceTestChannelListener<WebRTCAudioDeviceTest> { |
+ public: |
+ class SetupTask : public base::RefCountedThreadSafe<SetupTask> { |
+ public: |
+ explicit SetupTask(WebRTCAudioDeviceTest* test) : test_(test) {} |
+ void InitializeIOThread(const char* thread_name) { |
+ test_->InitializeIOThread(thread_name); |
+ } |
+ void UninitializeIOThread() { test_->UninitializeIOThread(); } |
+ protected: |
+ WebRTCAudioDeviceTest* test_; |
+ }; |
+ |
+ WebRTCAudioDeviceTest() : render_thread_(NULL), event_(false, false) {} |
+ virtual ~WebRTCAudioDeviceTest() {} |
+ |
+ 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
|
+ // Set low latency mode, as it soon would be on by default. |
+ if (AudioRendererImpl::latency_type() == |
+ AudioRendererImpl::kUninitializedLatency) { |
+ AudioRendererImpl::set_latency_type(AudioRendererImpl::kLowLatency); |
+ } |
+ DCHECK_EQ(AudioRendererImpl::kLowLatency, |
+ AudioRendererImpl::latency_type()); |
+ } |
+ |
+ virtual void SetUp() { |
+ // This part sets up a RenderThread environment to ensure that |
+ // RenderThread::current() (<=> TLS pointer) is valid. |
+ // Main parts are inspired by the RenderViewFakeResourcesTest. |
+ // Note that, the IPC part is not utilized in this test. |
+ 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
|
+ mock_process_.reset(new MockRenderProcess); |
+ ui_thread_.reset(new content::TestBrowserThread(BrowserThread::UI, |
+ MessageLoop::current())); |
+ |
+ // Construct the resource context on the UI thread. |
+ resource_context_.reset(new WebRTCMockResourceContext()); |
+ |
+ static const char kThreadName[] = "RenderThread"; |
+ ChildProcess::current()->io_message_loop()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&SetupTask::InitializeIOThread, new SetupTask(this), |
+ kThreadName)); |
+ WaitForIOThreadCompletion(); |
+ |
+ render_thread_ = new RenderThreadImpl(kThreadName); |
+ mock_process_->set_main_thread(render_thread_); |
+ } |
+ |
+ virtual void TearDown() { |
+ ChildProcess::current()->io_message_loop()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&SetupTask::UninitializeIOThread, new SetupTask(this))); |
+ WaitForIOThreadCompletion(); |
+ mock_process_.reset(); |
+ } |
+ |
+ RenderThreadImpl* render_thread() const { |
+ return render_thread_; |
+ } |
+ |
+ protected: |
+ void InitializeIOThread(const char* thread_name) { |
+ // Set the current thread as the IO thread. |
+ io_thread_.reset(new content::TestBrowserThread(BrowserThread::IO, |
+ MessageLoop::current())); |
+ test_request_context_ = new TestURLRequestContext(); |
+ resource_context_->set_request_context(test_request_context_.get()); |
+ media_observer_.reset(new MockMediaObserver()); |
+ resource_context_->set_media_observer(media_observer_.get()); |
+ |
+ CreateChannel(thread_name, resource_context_.get()); |
+ } |
+ |
+ void UninitializeIOThread() { |
+ DestroyChannel(); |
+ resource_context_.reset(); |
+ test_request_context_ = NULL; |
+ } |
+ |
+ // Posts a final task to the IO message loop and waits for completion. |
+ void WaitForIOThreadCompletion() { |
+ ChildProcess::current()->io_message_loop()->PostTask( |
+ FROM_HERE, new SignalTask(&event_)); |
+ EXPECT_TRUE(event_.TimedWait( |
+ base::TimeDelta::FromMilliseconds(TestTimeouts::action_timeout_ms()))); |
+ } |
+ |
+ // Convenience getter for gmock. |
+ MockMediaObserver& media_observer() const { |
+ return *media_observer_.get(); |
+ } |
+ |
+ std::string GetTestDataPath(const FilePath::StringType& file_name) { |
+ FilePath path; |
+ 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.
|
+ path = path.Append(file_name); |
+#ifdef OS_WIN |
+ return WideToUTF8(path.value()); |
+#else |
+ return path.value(); |
+#endif |
+ } |
+ |
+ // Actual implementation of the short and long PlayFile tests. |
+ // If the |duration| parameter is 0, then the function determines the length |
+ // of the audio file and plays the entire file. Otherwise, |duration| |
+ // specifies the number of milliseconds to allow the file to play. |
+ void PlayLocalFile(int duration); |
+ |
+ MessageLoopForUI message_loop_; |
+ content::MockContentRendererClient mock_content_renderer_client_; |
+ RenderThreadImpl* render_thread_; // owned by mock_process_ |
+ scoped_ptr<MockRenderProcess> mock_process_; |
+ media::MockFilterHost host_; |
+ base::WaitableEvent event_; |
+ scoped_ptr<MockMediaObserver> media_observer_; |
+ scoped_ptr<content::ResourceContext> resource_context_; |
+ scoped_refptr<net::URLRequestContext> test_request_context_; |
+ |
+ // Initialized on the main test thread that we mark as the UI thread. |
+ scoped_ptr<content::TestBrowserThread> ui_thread_; |
+ // Initialized on our IO thread to satisfy BrowserThread::IO checks. |
+ scoped_ptr<content::TestBrowserThread> io_thread_; |
+ |
+ private: |
+ 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.
|
+}; |
+ |
+// A very basic implementation of webrtc::Transport that acts as a transport |
+// but just forwards all calls to a local webrtc::VoENetwork implementation. |
+// Ownership of the VoENetwork object lies outside the class. |
+class TransportImpl : public webrtc::Transport { |
+ public: |
+ explicit TransportImpl(webrtc::VoENetwork* network) : network_(network) {} |
+ ~TransportImpl() {} |
+ |
+ virtual int SendPacket(int channel, const void* data, int len) { |
+ DVLOG(1) << __FUNCTION__; |
+ return network_->ReceivedRTPPacket(channel, data, len); |
+ } |
+ |
+ virtual int SendRTCPPacket(int channel, const void* data, int len) { |
+ DVLOG(1) << __FUNCTION__; |
+ return network_->ReceivedRTCPPacket(channel, data, len); |
+ } |
+ |
+ private: |
+ webrtc::VoENetwork* network_; |
+}; |
+ |
+// Basic test that instantiates and initializes an instance of |
+// WebRtcAudioDeviceImpl. |
+TEST_F(WebRTCAudioDeviceTest, Construct) { |
+ scoped_refptr<WebRtcAudioDeviceImpl> audio_device( |
+ new WebRtcAudioDeviceImpl()); |
+ audio_device->SetSessionId(1); |
+ |
+ AutoDelete<webrtc::VoiceEngine> engine(webrtc::VoiceEngine::Create()); |
+ |
+ ScopedWebRTCPtr<webrtc::VoEBase> base(engine.get()); |
+ int err = base->Init(audio_device); |
+ EXPECT_EQ(0, err); |
+} |
+ |
+void WebRTCAudioDeviceTest::PlayLocalFile(int duration) { |
+ EXPECT_GE(duration, 0); |
+ EXPECT_CALL(media_observer(), |
+ OnSetAudioStreamStatus(_, 1, StrEq("created"))).Times(1); |
+ |
+ EXPECT_CALL(media_observer(), |
+ OnSetAudioStreamPlaying(_, 1, true)).Times(1); |
+ |
+ // When the "closed" event is triggered, we end the test. |
+ EXPECT_CALL(media_observer(), |
+ OnSetAudioStreamStatus(_, 1, StrEq("closed"))) |
+ .WillOnce(QuitMessageLoop(message_loop_.message_loop_proxy())); |
+ |
+ EXPECT_CALL(media_observer(), |
+ OnDeleteAudioStream(_, 1)).Times(1); |
+ |
+ scoped_refptr<WebRtcAudioDeviceImpl> audio_device( |
+ new WebRtcAudioDeviceImpl()); |
+ audio_device->SetSessionId(1); |
+ |
+ AutoDelete<webrtc::VoiceEngine> engine(webrtc::VoiceEngine::Create()); |
+ |
+ ScopedWebRTCPtr<webrtc::VoEBase> base(engine.get()); |
+ int err = base->Init(audio_device); |
+ EXPECT_EQ(0, err); |
+ if (err == 0) { |
+ ScopedWebRTCPtr<webrtc::VoEAudioProcessing> audio_processing(engine.get()); |
+ EXPECT_EQ(0, audio_processing->SetAgcStatus(true, |
+ webrtc::kAgcAdaptiveDigital)); |
+ |
+ int ch = base->CreateChannel(); |
+ EXPECT_NE(-1, ch); |
+ |
+ ScopedWebRTCPtr<webrtc::VoENetwork> network(engine.get()); |
+ scoped_ptr<TransportImpl> transport(new TransportImpl(network.get())); |
+ EXPECT_EQ(0, network->RegisterExternalTransport(ch, *transport.get())); |
+ EXPECT_EQ(0, base->StartReceive(ch)); |
+ EXPECT_EQ(0, base->StartPlayout(ch)); |
+ EXPECT_EQ(0, base->StartSend(ch)); |
+ |
+ std::string file_path( |
+ GetTestDataPath(FILE_PATH_LITERAL("speechmusic_mono_16kHz.pcm"))); |
+ |
+ ScopedWebRTCPtr<webrtc::VoEFile> file(engine.get()); |
+ if (duration == 0) { |
+ EXPECT_EQ(0, file->GetFileDuration(file_path.c_str(), duration, |
+ webrtc::kFileFormatPcm16kHzFile)); |
+ EXPECT_NE(0, duration); |
+ } |
+ |
+ EXPECT_EQ(0, file->StartPlayingFileLocally(ch, file_path.c_str(), false, |
+ webrtc::kFileFormatPcm16kHzFile)); |
+ |
+ message_loop_.PostDelayedTask(FROM_HERE, |
+ new MessageLoop::QuitTask(), duration); |
+ message_loop_.Run(); |
+ |
+ EXPECT_EQ(0, network->DeRegisterExternalTransport(ch)); |
+ } |
+} |
+ |
+// Plays a local file. This test usually takes just under a minute to run |
+// and requires an audio card to work, so disabled by default. |
+TEST_F(WebRTCAudioDeviceTest, DISABLED_PlayLocalFileLong) { |
+ PlayLocalFile(0); |
+} |
+ |
+TEST_F(WebRTCAudioDeviceTest, PlayLocalFile) { |
+ PlayLocalFile(TestTimeouts::action_timeout_ms()); |
+} |
Property changes on: content/renderer/media/webrtc_audio_device_unittest.cc |
___________________________________________________________________ |
Added: svn:eol-style |
## -0,0 +1 ## |
+LF |