Chromium Code Reviews| 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 |