blob: 70e2a49c05f821b223d37a7018b4ade5d4162ef4 [file] [log] [blame]
// Copyright (c) 2012 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 <vector>
#include "base/at_exit.h"
#include "base/message_loop.h"
#include "base/process_util.h"
#include "base/shared_memory.h"
#include "base/sync_socket.h"
#include "base/test/test_timeouts.h"
#include "media/audio/audio_output_device.h"
#include "media/audio/sample_rates.h"
#include "media/audio/shared_memory_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gmock_mutant.h"
#include "testing/gtest/include/gtest/gtest.h"
using base::CancelableSyncSocket;
using base::SharedMemory;
using base::SyncSocket;
using testing::_;
using testing::DoAll;
using testing::Invoke;
using testing::Return;
using testing::WithArgs;
using testing::StrictMock;
using testing::Values;
namespace media {
namespace {
class MockRenderCallback : public AudioRendererSink::RenderCallback {
public:
MockRenderCallback() {}
virtual ~MockRenderCallback() {}
MOCK_METHOD2(Render, int(AudioBus* dest, int audio_delay_milliseconds));
MOCK_METHOD3(RenderIO, void(AudioBus* source,
AudioBus* dest,
int audio_delay_milliseconds));
MOCK_METHOD0(OnRenderError, void());
};
class MockAudioOutputIPC : public AudioOutputIPC {
public:
MockAudioOutputIPC() {}
virtual ~MockAudioOutputIPC() {}
MOCK_METHOD1(AddDelegate, int(AudioOutputIPCDelegate* delegate));
MOCK_METHOD1(RemoveDelegate, void(int stream_id));
MOCK_METHOD3(CreateStream,
void(int stream_id, const AudioParameters& params, int input_channels));
MOCK_METHOD1(PlayStream, void(int stream_id));
MOCK_METHOD1(CloseStream, void(int stream_id));
MOCK_METHOD2(SetVolume, void(int stream_id, double volume));
MOCK_METHOD1(PauseStream, void(int stream_id));
MOCK_METHOD1(FlushStream, void(int stream_id));
};
// Creates a copy of a SyncSocket handle that we can give to AudioOutputDevice.
// On Windows this means duplicating the pipe handle so that AudioOutputDevice
// can call CloseHandle() (since ownership has been transferred), but on other
// platforms, we just copy the same socket handle since AudioOutputDevice on
// those platforms won't actually own the socket (FileDescriptor.auto_close is
// false).
bool DuplicateSocketHandle(SyncSocket::Handle socket_handle,
SyncSocket::Handle* copy) {
#if defined(OS_WIN)
HANDLE process = GetCurrentProcess();
::DuplicateHandle(process, socket_handle, process, copy,
0, FALSE, DUPLICATE_SAME_ACCESS);
return *copy != NULL;
#else
*copy = socket_handle;
return *copy != -1;
#endif
}
ACTION_P2(SendPendingBytes, socket, pending_bytes) {
socket->Send(&pending_bytes, sizeof(pending_bytes));
}
// Used to terminate a loop from a different thread than the loop belongs to.
// |loop| should be a MessageLoopProxy.
ACTION_P(QuitLoop, loop) {
loop->PostTask(FROM_HERE, MessageLoop::QuitClosure());
}
} // namespace.
class AudioOutputDeviceTest
: public testing::Test,
public testing::WithParamInterface<bool> {
public:
AudioOutputDeviceTest();
~AudioOutputDeviceTest();
void StartAudioDevice();
void CreateStream();
void ExpectRenderCallback();
void WaitUntilRenderCallback();
void StopAudioDevice();
protected:
// Used to clean up TLS pointers that the test(s) will initialize.
// Must remain the first member of this class.
base::ShadowingAtExitManager at_exit_manager_;
MessageLoopForIO io_loop_;
const AudioParameters default_audio_parameters_;
StrictMock<MockRenderCallback> callback_;
StrictMock<MockAudioOutputIPC> audio_output_ipc_;
scoped_refptr<AudioOutputDevice> audio_device_;
private:
int CalculateMemorySize();
const bool synchronized_io_;
const int input_channels_;
SharedMemory shared_memory_;
CancelableSyncSocket browser_socket_;
CancelableSyncSocket renderer_socket_;
DISALLOW_COPY_AND_ASSIGN(AudioOutputDeviceTest);
};
static const int kStreamId = 123;
int AudioOutputDeviceTest::CalculateMemorySize() {
// Calculate output and input memory size.
int output_memory_size =
AudioBus::CalculateMemorySize(default_audio_parameters_);
int frames = default_audio_parameters_.frames_per_buffer();
int input_memory_size =
AudioBus::CalculateMemorySize(input_channels_, frames);
int io_buffer_size = output_memory_size + input_memory_size;
// This is where it gets a bit hacky. The shared memory contract between
// AudioOutputDevice and its browser side counter part includes a bit more
// than just the audio data, so we must call TotalSharedMemorySizeInBytes()
// to get the actual size needed to fit the audio data plus the extra data.
return TotalSharedMemorySizeInBytes(io_buffer_size);
}
AudioOutputDeviceTest::AudioOutputDeviceTest()
: default_audio_parameters_(AudioParameters::AUDIO_PCM_LINEAR,
CHANNEL_LAYOUT_STEREO,
48000, 16, 1024),
synchronized_io_(GetParam()),
input_channels_(synchronized_io_ ? 2 : 0) {
EXPECT_CALL(audio_output_ipc_, AddDelegate(_))
.WillOnce(Return(kStreamId));
audio_device_ = new AudioOutputDevice(
&audio_output_ipc_, io_loop_.message_loop_proxy());
if (synchronized_io_) {
audio_device_->InitializeIO(default_audio_parameters_,
input_channels_,
&callback_);
} else {
audio_device_->Initialize(default_audio_parameters_,
&callback_);
}
io_loop_.RunUntilIdle();
}
AudioOutputDeviceTest::~AudioOutputDeviceTest() {
EXPECT_CALL(audio_output_ipc_, RemoveDelegate(kStreamId));
audio_device_ = NULL;
}
void AudioOutputDeviceTest::StartAudioDevice() {
audio_device_->Start();
EXPECT_CALL(audio_output_ipc_, CreateStream(kStreamId, _, _));
io_loop_.RunUntilIdle();
}
void AudioOutputDeviceTest::CreateStream() {
const int kMemorySize = CalculateMemorySize();
ASSERT_TRUE(shared_memory_.CreateAndMapAnonymous(kMemorySize));
memset(shared_memory_.memory(), 0xff, kMemorySize);
ASSERT_TRUE(CancelableSyncSocket::CreatePair(&browser_socket_,
&renderer_socket_));
// Create duplicates of the handles we pass to AudioOutputDevice since
// ownership will be transferred and AudioOutputDevice is responsible for
// freeing.
SyncSocket::Handle audio_device_socket = SyncSocket::kInvalidHandle;
ASSERT_TRUE(DuplicateSocketHandle(renderer_socket_.handle(),
&audio_device_socket));
base::SharedMemoryHandle duplicated_memory_handle;
ASSERT_TRUE(shared_memory_.ShareToProcess(base::GetCurrentProcessHandle(),
&duplicated_memory_handle));
audio_device_->OnStreamCreated(duplicated_memory_handle, audio_device_socket,
PacketSizeInBytes(kMemorySize));
io_loop_.RunUntilIdle();
}
void AudioOutputDeviceTest::ExpectRenderCallback() {
// We should get a 'play' notification when we call OnStreamCreated().
// Respond by asking for some audio data. This should ask our callback
// to provide some audio data that AudioOutputDevice then writes into the
// shared memory section.
const int kMemorySize = CalculateMemorySize();
EXPECT_CALL(audio_output_ipc_, PlayStream(kStreamId))
.WillOnce(SendPendingBytes(&browser_socket_, kMemorySize));
// We expect calls to our audio renderer callback, which returns the number
// of frames written to the memory section.
// Here's the second place where it gets hacky: There's no way for us to
// know (without using a sleep loop!) when the AudioOutputDevice has finished
// writing the interleaved audio data into the shared memory section.
// So, for the sake of this test, we consider the call to Render a sign
// of success and quit the loop.
if (synchronized_io_) {
// For synchronized I/O, we expect RenderIO().
EXPECT_CALL(callback_, RenderIO(_, _, _))
.WillOnce(QuitLoop(io_loop_.message_loop_proxy()));
} else {
// For output only we expect Render().
const int kNumberOfFramesToProcess = 0;
EXPECT_CALL(callback_, Render(_, _))
.WillOnce(DoAll(
QuitLoop(io_loop_.message_loop_proxy()),
Return(kNumberOfFramesToProcess)));
}
}
void AudioOutputDeviceTest::WaitUntilRenderCallback() {
// Don't hang the test if we never get the Render() callback.
io_loop_.PostDelayedTask(FROM_HERE, MessageLoop::QuitClosure(),
TestTimeouts::action_timeout());
io_loop_.Run();
}
void AudioOutputDeviceTest::StopAudioDevice() {
audio_device_->Stop();
EXPECT_CALL(audio_output_ipc_, CloseStream(kStreamId));
io_loop_.RunUntilIdle();
}
TEST_P(AudioOutputDeviceTest, Initialize) {
// Tests that the object can be constructed, initialized and destructed
// without having ever been started/stopped.
}
// Calls Start() followed by an immediate Stop() and check for the basic message
// filter messages being sent in that case.
TEST_P(AudioOutputDeviceTest, StartStop) {
StartAudioDevice();
StopAudioDevice();
}
// AudioOutputDevice supports multiple start/stop sequences.
TEST_P(AudioOutputDeviceTest, StartStopStartStop) {
StartAudioDevice();
StopAudioDevice();
StartAudioDevice();
StopAudioDevice();
}
// Simulate receiving OnStreamCreated() prior to processing ShutDownOnIOThread()
// on the IO loop.
TEST_P(AudioOutputDeviceTest, StopBeforeRender) {
StartAudioDevice();
// Call Stop() but don't run the IO loop yet.
audio_device_->Stop();
// Expect us to shutdown IPC but not to render anything despite the stream
// getting created.
EXPECT_CALL(audio_output_ipc_, CloseStream(kStreamId));
CreateStream();
}
// Full test with output only.
TEST_P(AudioOutputDeviceTest, CreateStream) {
StartAudioDevice();
ExpectRenderCallback();
CreateStream();
WaitUntilRenderCallback();
StopAudioDevice();
}
INSTANTIATE_TEST_CASE_P(Render, AudioOutputDeviceTest, Values(false));
INSTANTIATE_TEST_CASE_P(RenderIO, AudioOutputDeviceTest, Values(true));
} // namespace media.