blob: 1db97afb8948a7c91c56a5e52be06a1c7ee38db8 [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 "base/message_loop.h"
#include "base/stringprintf.h"
#include "media/audio/linux/alsa_output.h"
#include "media/audio/linux/alsa_wrapper.h"
#include "media/audio/linux/audio_manager_linux.h"
#include "media/base/data_buffer.h"
#include "media/base/seekable_buffer.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
using testing::AllOf;
using testing::AtLeast;
using testing::DoAll;
using testing::Field;
using testing::InSequence;
using testing::Invoke;
using testing::InvokeWithoutArgs;
using testing::Mock;
using testing::MockFunction;
using testing::Return;
using testing::SetArgumentPointee;
using testing::StrictMock;
using testing::StrEq;
using testing::Unused;
namespace media {
class MockAlsaWrapper : public AlsaWrapper {
public:
MOCK_METHOD3(DeviceNameHint, int(int card,
const char* iface,
void*** hints));
MOCK_METHOD2(DeviceNameGetHint, char*(const void* hint, const char* id));
MOCK_METHOD1(DeviceNameFreeHint, int(void** hints));
MOCK_METHOD4(PcmOpen, int(snd_pcm_t** handle, const char* name,
snd_pcm_stream_t stream, int mode));
MOCK_METHOD1(PcmClose, int(snd_pcm_t* handle));
MOCK_METHOD1(PcmPrepare, int(snd_pcm_t* handle));
MOCK_METHOD1(PcmDrop, int(snd_pcm_t* handle));
MOCK_METHOD2(PcmDelay, int(snd_pcm_t* handle, snd_pcm_sframes_t* delay));
MOCK_METHOD3(PcmWritei, snd_pcm_sframes_t(snd_pcm_t* handle,
const void* buffer,
snd_pcm_uframes_t size));
MOCK_METHOD3(PcmReadi, snd_pcm_sframes_t(snd_pcm_t* handle,
void* buffer,
snd_pcm_uframes_t size));
MOCK_METHOD3(PcmRecover, int(snd_pcm_t* handle, int err, int silent));
MOCK_METHOD7(PcmSetParams, int(snd_pcm_t* handle, snd_pcm_format_t format,
snd_pcm_access_t access, unsigned int channels,
unsigned int rate, int soft_resample,
unsigned int latency));
MOCK_METHOD3(PcmGetParams, int(snd_pcm_t* handle,
snd_pcm_uframes_t* buffer_size,
snd_pcm_uframes_t* period_size));
MOCK_METHOD1(PcmName, const char*(snd_pcm_t* handle));
MOCK_METHOD1(PcmAvailUpdate, snd_pcm_sframes_t(snd_pcm_t* handle));
MOCK_METHOD1(PcmState, snd_pcm_state_t(snd_pcm_t* handle));
MOCK_METHOD1(PcmStart, int(snd_pcm_t* handle));
MOCK_METHOD1(StrError, const char*(int errnum));
};
class MockAudioSourceCallback : public AudioOutputStream::AudioSourceCallback {
public:
MOCK_METHOD2(OnMoreData, int(AudioBus* audio_bus,
AudioBuffersState buffers_state));
MOCK_METHOD3(OnMoreIOData, int(AudioBus* source,
AudioBus* dest,
AudioBuffersState buffers_state));
MOCK_METHOD2(OnError, void(AudioOutputStream* stream, int code));
};
class MockAudioManagerLinux : public AudioManagerLinux {
public:
MOCK_METHOD0(Init, void());
MOCK_METHOD0(HasAudioOutputDevices, bool());
MOCK_METHOD0(HasAudioInputDevices, bool());
MOCK_METHOD1(MakeLinearOutputStream, AudioOutputStream*(
const AudioParameters& params));
MOCK_METHOD1(MakeLowLatencyOutputStream, AudioOutputStream*(
const AudioParameters& params));
MOCK_METHOD2(MakeLowLatencyInputStream, AudioInputStream*(
const AudioParameters& params, const std::string& device_id));
// We need to override this function in order to skip the checking the number
// of active output streams. It is because the number of active streams
// is managed inside MakeAudioOutputStream, and we don't use
// MakeAudioOutputStream to create the stream in the tests.
virtual void ReleaseOutputStream(AudioOutputStream* stream) OVERRIDE {
DCHECK(stream);
delete stream;
}
// We don't mock this method since all tests will do the same thing
// and use the current message loop.
virtual scoped_refptr<base::MessageLoopProxy> GetMessageLoop() OVERRIDE {
return MessageLoop::current()->message_loop_proxy();
}
};
class AlsaPcmOutputStreamTest : public testing::Test {
protected:
AlsaPcmOutputStreamTest() {
mock_manager_.reset(new StrictMock<MockAudioManagerLinux>());
}
virtual ~AlsaPcmOutputStreamTest() {
}
AlsaPcmOutputStream* CreateStream(ChannelLayout layout) {
return CreateStream(layout, kTestFramesPerPacket);
}
AlsaPcmOutputStream* CreateStream(ChannelLayout layout,
int32 samples_per_packet) {
AudioParameters params(kTestFormat, layout, kTestSampleRate,
kTestBitsPerSample, samples_per_packet);
return new AlsaPcmOutputStream(kTestDeviceName,
params,
&mock_alsa_wrapper_,
mock_manager_.get());
}
// Helper function to malloc the string returned by DeviceNameHint for NAME.
static char* EchoHint(const void* name, Unused) {
return strdup(static_cast<const char*>(name));
}
// Helper function to malloc the string returned by DeviceNameHint for IOID.
static char* OutputHint(Unused, Unused) {
return strdup("Output");
}
// Helper function to initialize |test_stream->buffer_|. Must be called
// in all tests that use buffer_ without opening the stream.
void InitBuffer(AlsaPcmOutputStream* test_stream) {
DCHECK(test_stream);
packet_ = new media::DataBuffer(kTestPacketSize);
packet_->SetDataSize(kTestPacketSize);
test_stream->buffer_.reset(new media::SeekableBuffer(0, kTestPacketSize));
test_stream->buffer_->Append(packet_.get());
}
static const ChannelLayout kTestChannelLayout;
static const int kTestSampleRate;
static const int kTestBitsPerSample;
static const int kTestBytesPerFrame;
static const AudioParameters::Format kTestFormat;
static const char kTestDeviceName[];
static const char kDummyMessage[];
static const uint32 kTestFramesPerPacket;
static const int kTestPacketSize;
static const int kTestFailedErrno;
static snd_pcm_t* const kFakeHandle;
// Used to simulate DeviceNameHint.
static char kSurround40[];
static char kSurround41[];
static char kSurround50[];
static char kSurround51[];
static char kSurround70[];
static char kSurround71[];
static void* kFakeHints[];
StrictMock<MockAlsaWrapper> mock_alsa_wrapper_;
scoped_ptr<StrictMock<MockAudioManagerLinux> > mock_manager_;
MessageLoop message_loop_;
scoped_refptr<media::DataBuffer> packet_;
private:
DISALLOW_COPY_AND_ASSIGN(AlsaPcmOutputStreamTest);
};
const ChannelLayout AlsaPcmOutputStreamTest::kTestChannelLayout =
CHANNEL_LAYOUT_STEREO;
const int AlsaPcmOutputStreamTest::kTestSampleRate =
AudioParameters::kAudioCDSampleRate;
const int AlsaPcmOutputStreamTest::kTestBitsPerSample = 8;
const int AlsaPcmOutputStreamTest::kTestBytesPerFrame =
AlsaPcmOutputStreamTest::kTestBitsPerSample / 8 *
ChannelLayoutToChannelCount(AlsaPcmOutputStreamTest::kTestChannelLayout);
const AudioParameters::Format AlsaPcmOutputStreamTest::kTestFormat =
AudioParameters::AUDIO_PCM_LINEAR;
const char AlsaPcmOutputStreamTest::kTestDeviceName[] = "TestDevice";
const char AlsaPcmOutputStreamTest::kDummyMessage[] = "dummy";
const uint32 AlsaPcmOutputStreamTest::kTestFramesPerPacket = 1000;
const int AlsaPcmOutputStreamTest::kTestPacketSize =
AlsaPcmOutputStreamTest::kTestFramesPerPacket *
AlsaPcmOutputStreamTest::kTestBytesPerFrame;
const int AlsaPcmOutputStreamTest::kTestFailedErrno = -EACCES;
snd_pcm_t* const AlsaPcmOutputStreamTest::kFakeHandle =
reinterpret_cast<snd_pcm_t*>(1);
char AlsaPcmOutputStreamTest::kSurround40[] = "surround40:CARD=foo,DEV=0";
char AlsaPcmOutputStreamTest::kSurround41[] = "surround41:CARD=foo,DEV=0";
char AlsaPcmOutputStreamTest::kSurround50[] = "surround50:CARD=foo,DEV=0";
char AlsaPcmOutputStreamTest::kSurround51[] = "surround51:CARD=foo,DEV=0";
char AlsaPcmOutputStreamTest::kSurround70[] = "surround70:CARD=foo,DEV=0";
char AlsaPcmOutputStreamTest::kSurround71[] = "surround71:CARD=foo,DEV=0";
void* AlsaPcmOutputStreamTest::kFakeHints[] = {
kSurround40, kSurround41, kSurround50, kSurround51,
kSurround70, kSurround71, NULL };
// Custom action to clear a memory buffer.
ACTION(ClearBuffer) {
arg0->Zero();
}
TEST_F(AlsaPcmOutputStreamTest, ConstructedState) {
AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
EXPECT_EQ(AlsaPcmOutputStream::kCreated, test_stream->state());
test_stream->Close();
// Should support mono.
test_stream = CreateStream(CHANNEL_LAYOUT_MONO);
EXPECT_EQ(AlsaPcmOutputStream::kCreated, test_stream->state());
test_stream->Close();
// Should support multi-channel.
test_stream = CreateStream(CHANNEL_LAYOUT_SURROUND);
EXPECT_EQ(AlsaPcmOutputStream::kCreated, test_stream->state());
test_stream->Close();
// Bad bits per sample.
AudioParameters bad_bps_params(kTestFormat, kTestChannelLayout,
kTestSampleRate, kTestBitsPerSample - 1,
kTestFramesPerPacket);
test_stream = new AlsaPcmOutputStream(kTestDeviceName,
bad_bps_params,
&mock_alsa_wrapper_,
mock_manager_.get());
EXPECT_EQ(AlsaPcmOutputStream::kInError, test_stream->state());
test_stream->Close();
// Bad format.
AudioParameters bad_format_params(
AudioParameters::AUDIO_LAST_FORMAT, kTestChannelLayout, kTestSampleRate,
kTestBitsPerSample, kTestFramesPerPacket);
test_stream = new AlsaPcmOutputStream(kTestDeviceName,
bad_format_params,
&mock_alsa_wrapper_,
mock_manager_.get());
EXPECT_EQ(AlsaPcmOutputStream::kInError, test_stream->state());
test_stream->Close();
}
TEST_F(AlsaPcmOutputStreamTest, LatencyFloor) {
const double kMicrosPerFrame =
static_cast<double>(1000000) / kTestSampleRate;
const double kPacketFramesInMinLatency =
AlsaPcmOutputStream::kMinLatencyMicros / kMicrosPerFrame / 2.0;
// Test that packets which would cause a latency under less than
// AlsaPcmOutputStream::kMinLatencyMicros will get clipped to
// AlsaPcmOutputStream::kMinLatencyMicros,
EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
.WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle),
Return(0)));
EXPECT_CALL(mock_alsa_wrapper_,
PcmSetParams(_, _, _, _, _, _,
AlsaPcmOutputStream::kMinLatencyMicros))
.WillOnce(Return(0));
EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _))
.WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket),
SetArgumentPointee<2>(kTestFramesPerPacket / 2),
Return(0)));
AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout,
kPacketFramesInMinLatency);
ASSERT_TRUE(test_stream->Open());
// Now close it and test that everything was released.
EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle)).WillOnce(Return(0));
EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle))
.WillOnce(Return(kTestDeviceName));
test_stream->Close();
Mock::VerifyAndClear(&mock_alsa_wrapper_);
Mock::VerifyAndClear(mock_manager_.get());
// Test that having more packets ends up with a latency based on packet size.
const int kOverMinLatencyPacketSize = kPacketFramesInMinLatency + 1;
int64 expected_micros = 2 * AlsaPcmOutputStream::FramesToMicros(
kOverMinLatencyPacketSize, kTestSampleRate);
EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
.WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), Return(0)));
EXPECT_CALL(mock_alsa_wrapper_,
PcmSetParams(_, _, _, _, _, _, expected_micros))
.WillOnce(Return(0));
EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _))
.WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket),
SetArgumentPointee<2>(kTestFramesPerPacket / 2),
Return(0)));
test_stream = CreateStream(kTestChannelLayout,
kOverMinLatencyPacketSize);
ASSERT_TRUE(test_stream->Open());
// Now close it and test that everything was released.
EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle))
.WillOnce(Return(0));
EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle))
.WillOnce(Return(kTestDeviceName));
test_stream->Close();
Mock::VerifyAndClear(&mock_alsa_wrapper_);
Mock::VerifyAndClear(mock_manager_.get());
}
TEST_F(AlsaPcmOutputStreamTest, OpenClose) {
int64 expected_micros = 2 *
AlsaPcmOutputStream::FramesToMicros(kTestPacketSize / kTestBytesPerFrame,
kTestSampleRate);
// Open() call opens the playback device, sets the parameters, posts a task
// with the resulting configuration data, and transitions the object state to
// kIsOpened.
EXPECT_CALL(mock_alsa_wrapper_,
PcmOpen(_, StrEq(kTestDeviceName),
SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK))
.WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle),
Return(0)));
EXPECT_CALL(mock_alsa_wrapper_,
PcmSetParams(kFakeHandle,
SND_PCM_FORMAT_U8,
SND_PCM_ACCESS_RW_INTERLEAVED,
ChannelLayoutToChannelCount(kTestChannelLayout),
kTestSampleRate,
1,
expected_micros))
.WillOnce(Return(0));
EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(kFakeHandle, _, _))
.WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket),
SetArgumentPointee<2>(kTestFramesPerPacket / 2),
Return(0)));
// Open the stream.
AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
ASSERT_TRUE(test_stream->Open());
EXPECT_EQ(AlsaPcmOutputStream::kIsOpened, test_stream->state());
EXPECT_EQ(kFakeHandle, test_stream->playback_handle_);
EXPECT_EQ(kTestFramesPerPacket, test_stream->frames_per_packet_);
EXPECT_TRUE(test_stream->buffer_.get());
EXPECT_FALSE(test_stream->stop_stream_);
// Now close it and test that everything was released.
EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle))
.WillOnce(Return(0));
EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle))
.WillOnce(Return(kTestDeviceName));
test_stream->Close();
}
TEST_F(AlsaPcmOutputStreamTest, PcmOpenFailed) {
EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
.WillOnce(Return(kTestFailedErrno));
EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno))
.WillOnce(Return(kDummyMessage));
AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
ASSERT_FALSE(test_stream->Open());
ASSERT_EQ(AlsaPcmOutputStream::kInError, test_stream->state());
// Ensure internal state is set for a no-op stream if PcmOpen() failes.
EXPECT_TRUE(test_stream->stop_stream_);
EXPECT_TRUE(test_stream->playback_handle_ == NULL);
EXPECT_FALSE(test_stream->buffer_.get());
// Close the stream since we opened it to make destruction happy.
test_stream->Close();
}
TEST_F(AlsaPcmOutputStreamTest, PcmSetParamsFailed) {
EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
.WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle),
Return(0)));
EXPECT_CALL(mock_alsa_wrapper_, PcmSetParams(_, _, _, _, _, _, _))
.WillOnce(Return(kTestFailedErrno));
EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle))
.WillOnce(Return(0));
EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle))
.WillOnce(Return(kTestDeviceName));
EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno))
.WillOnce(Return(kDummyMessage));
// If open fails, the stream stays in kCreated because it has effectively had
// no changes.
AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
ASSERT_FALSE(test_stream->Open());
EXPECT_EQ(AlsaPcmOutputStream::kInError, test_stream->state());
// Ensure internal state is set for a no-op stream if PcmSetParams() failes.
EXPECT_TRUE(test_stream->stop_stream_);
EXPECT_TRUE(test_stream->playback_handle_ == NULL);
EXPECT_FALSE(test_stream->buffer_.get());
// Close the stream since we opened it to make destruction happy.
test_stream->Close();
}
TEST_F(AlsaPcmOutputStreamTest, StartStop) {
// Open() call opens the playback device, sets the parameters, posts a task
// with the resulting configuration data, and transitions the object state to
// kIsOpened.
EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
.WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle),
Return(0)));
EXPECT_CALL(mock_alsa_wrapper_, PcmSetParams(_, _, _, _, _, _, _))
.WillOnce(Return(0));
EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _))
.WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket),
SetArgumentPointee<2>(kTestFramesPerPacket / 2),
Return(0)));
// Open the stream.
AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
ASSERT_TRUE(test_stream->Open());
// Expect Device setup.
EXPECT_CALL(mock_alsa_wrapper_, PcmDrop(kFakeHandle))
.WillOnce(Return(0));
EXPECT_CALL(mock_alsa_wrapper_, PcmPrepare(kFakeHandle))
.WillOnce(Return(0));
// Expect the pre-roll.
MockAudioSourceCallback mock_callback;
EXPECT_CALL(mock_alsa_wrapper_, PcmState(kFakeHandle))
.WillRepeatedly(Return(SND_PCM_STATE_RUNNING));
EXPECT_CALL(mock_alsa_wrapper_, PcmDelay(kFakeHandle, _))
.WillRepeatedly(DoAll(SetArgumentPointee<1>(0), Return(0)));
EXPECT_CALL(mock_callback, OnMoreData(_, _))
.WillRepeatedly(DoAll(ClearBuffer(), Return(kTestFramesPerPacket)));
EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(kFakeHandle, _, _))
.WillRepeatedly(Return(kTestFramesPerPacket));
// Expect scheduling.
EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle))
.Times(AtLeast(2))
.WillRepeatedly(Return(kTestFramesPerPacket));
test_stream->Start(&mock_callback);
// Start() will issue a WriteTask() directly and then schedule the next one,
// call Stop() immediately after to ensure we don't run the message loop
// forever.
test_stream->Stop();
message_loop_.RunUntilIdle();
EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle))
.WillOnce(Return(0));
EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle))
.WillOnce(Return(kTestDeviceName));
test_stream->Close();
}
TEST_F(AlsaPcmOutputStreamTest, WritePacket_FinishedPacket) {
AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
InitBuffer(test_stream);
test_stream->TransitionTo(AlsaPcmOutputStream::kIsOpened);
test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying);
// Nothing should happen. Don't set any expectations and Our strict mocks
// should verify most of this.
// Test empty buffer.
test_stream->buffer_->Clear();
test_stream->WritePacket();
test_stream->Close();
}
TEST_F(AlsaPcmOutputStreamTest, WritePacket_NormalPacket) {
// We need to open the stream before writing data to ALSA.
EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
.WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle),
Return(0)));
EXPECT_CALL(mock_alsa_wrapper_, PcmSetParams(_, _, _, _, _, _, _))
.WillOnce(Return(0));
EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _))
.WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket),
SetArgumentPointee<2>(kTestFramesPerPacket / 2),
Return(0)));
AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
ASSERT_TRUE(test_stream->Open());
InitBuffer(test_stream);
test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying);
// Write a little less than half the data.
int written = packet_->GetDataSize() / kTestBytesPerFrame / 2 - 1;
EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle))
.WillOnce(Return(written));
EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(kFakeHandle, packet_->GetData(), _))
.WillOnce(Return(written));
test_stream->WritePacket();
ASSERT_EQ(test_stream->buffer_->forward_bytes(),
packet_->GetDataSize() - written * kTestBytesPerFrame);
// Write the rest.
EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle))
.WillOnce(Return(kTestFramesPerPacket - written));
EXPECT_CALL(mock_alsa_wrapper_,
PcmWritei(kFakeHandle,
packet_->GetData() + written * kTestBytesPerFrame,
_))
.WillOnce(Return(packet_->GetDataSize() / kTestBytesPerFrame - written));
test_stream->WritePacket();
EXPECT_EQ(0, test_stream->buffer_->forward_bytes());
// Now close it and test that everything was released.
EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle))
.WillOnce(Return(0));
EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle))
.WillOnce(Return(kTestDeviceName));
test_stream->Close();
}
TEST_F(AlsaPcmOutputStreamTest, WritePacket_WriteFails) {
// We need to open the stream before writing data to ALSA.
EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
.WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle),
Return(0)));
EXPECT_CALL(mock_alsa_wrapper_, PcmSetParams(_, _, _, _, _, _, _))
.WillOnce(Return(0));
EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _))
.WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket),
SetArgumentPointee<2>(kTestFramesPerPacket / 2),
Return(0)));
AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
ASSERT_TRUE(test_stream->Open());
InitBuffer(test_stream);
test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying);
// Fail due to a recoverable error and see that PcmRecover code path
// continues normally.
EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle))
.WillOnce(Return(kTestFramesPerPacket));
EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(kFakeHandle, _, _))
.WillOnce(Return(-EINTR));
EXPECT_CALL(mock_alsa_wrapper_, PcmRecover(kFakeHandle, _, _))
.WillOnce(Return(0));
test_stream->WritePacket();
ASSERT_EQ(test_stream->buffer_->forward_bytes(), packet_->GetDataSize());
// Fail the next write, and see that stop_stream_ is set.
EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle))
.WillOnce(Return(kTestFramesPerPacket));
EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(kFakeHandle, _, _))
.WillOnce(Return(kTestFailedErrno));
EXPECT_CALL(mock_alsa_wrapper_, PcmRecover(kFakeHandle, _, _))
.WillOnce(Return(kTestFailedErrno));
EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno))
.WillOnce(Return(kDummyMessage));
test_stream->WritePacket();
EXPECT_EQ(test_stream->buffer_->forward_bytes(), packet_->GetDataSize());
EXPECT_TRUE(test_stream->stop_stream_);
// Now close it and test that everything was released.
EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle))
.WillOnce(Return(0));
EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle))
.WillOnce(Return(kTestDeviceName));
test_stream->Close();
}
TEST_F(AlsaPcmOutputStreamTest, WritePacket_StopStream) {
AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
InitBuffer(test_stream);
test_stream->TransitionTo(AlsaPcmOutputStream::kIsOpened);
test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying);
// No expectations set on the strict mock because nothing should be called.
test_stream->stop_stream_ = true;
test_stream->WritePacket();
EXPECT_EQ(0, test_stream->buffer_->forward_bytes());
test_stream->Close();
}
TEST_F(AlsaPcmOutputStreamTest, BufferPacket) {
AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
InitBuffer(test_stream);
test_stream->buffer_->Clear();
MockAudioSourceCallback mock_callback;
EXPECT_CALL(mock_alsa_wrapper_, PcmState(_))
.WillOnce(Return(SND_PCM_STATE_RUNNING));
EXPECT_CALL(mock_alsa_wrapper_, PcmDelay(_, _))
.WillOnce(DoAll(SetArgumentPointee<1>(1), Return(0)));
EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(_))
.WillRepeatedly(Return(0)); // Buffer is full.
// Return a partially filled packet.
EXPECT_CALL(mock_callback, OnMoreData(_, _))
.WillOnce(DoAll(ClearBuffer(), Return(kTestFramesPerPacket / 2)));
bool source_exhausted;
test_stream->set_source_callback(&mock_callback);
test_stream->packet_size_ = kTestPacketSize;
test_stream->BufferPacket(&source_exhausted);
EXPECT_EQ(kTestPacketSize / 2, test_stream->buffer_->forward_bytes());
EXPECT_FALSE(source_exhausted);
test_stream->Close();
}
TEST_F(AlsaPcmOutputStreamTest, BufferPacket_Negative) {
AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
InitBuffer(test_stream);
test_stream->buffer_->Clear();
// Simulate where the underrun has occurred right after checking the delay.
MockAudioSourceCallback mock_callback;
EXPECT_CALL(mock_alsa_wrapper_, PcmState(_))
.WillOnce(Return(SND_PCM_STATE_RUNNING));
EXPECT_CALL(mock_alsa_wrapper_, PcmDelay(_, _))
.WillOnce(DoAll(SetArgumentPointee<1>(-1), Return(0)));
EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(_))
.WillRepeatedly(Return(0)); // Buffer is full.
EXPECT_CALL(mock_callback, OnMoreData(_, _))
.WillOnce(DoAll(ClearBuffer(), Return(kTestFramesPerPacket / 2)));
bool source_exhausted;
test_stream->set_source_callback(&mock_callback);
test_stream->packet_size_ = kTestPacketSize;
test_stream->BufferPacket(&source_exhausted);
EXPECT_EQ(kTestPacketSize / 2, test_stream->buffer_->forward_bytes());
EXPECT_FALSE(source_exhausted);
test_stream->Close();
}
TEST_F(AlsaPcmOutputStreamTest, BufferPacket_Underrun) {
AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
InitBuffer(test_stream);
test_stream->buffer_->Clear();
// If ALSA has underrun then we should assume a delay of zero.
MockAudioSourceCallback mock_callback;
EXPECT_CALL(mock_alsa_wrapper_, PcmState(_))
.WillOnce(Return(SND_PCM_STATE_XRUN));
EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(_))
.WillRepeatedly(Return(0)); // Buffer is full.
EXPECT_CALL(mock_callback,
OnMoreData(_, AllOf(
Field(&AudioBuffersState::pending_bytes, 0),
Field(&AudioBuffersState::hardware_delay_bytes, 0))))
.WillOnce(DoAll(ClearBuffer(), Return(kTestFramesPerPacket / 2)));
bool source_exhausted;
test_stream->set_source_callback(&mock_callback);
test_stream->packet_size_ = kTestPacketSize;
test_stream->BufferPacket(&source_exhausted);
EXPECT_EQ(kTestPacketSize / 2, test_stream->buffer_->forward_bytes());
EXPECT_FALSE(source_exhausted);
test_stream->Close();
}
TEST_F(AlsaPcmOutputStreamTest, BufferPacket_FullBuffer) {
AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
InitBuffer(test_stream);
// No expectations set on the strict mock because nothing should be called.
bool source_exhausted;
test_stream->packet_size_ = kTestPacketSize;
test_stream->BufferPacket(&source_exhausted);
EXPECT_EQ(kTestPacketSize, test_stream->buffer_->forward_bytes());
EXPECT_FALSE(source_exhausted);
test_stream->Close();
}
TEST_F(AlsaPcmOutputStreamTest, AutoSelectDevice_DeviceSelect) {
// Try channels from 1 -> 9. and see that we get the more specific surroundXX
// device opened for channels 4-8. For all other channels, the device should
// default to |AlsaPcmOutputStream::kDefaultDevice|. We should also not
// downmix any channel in this case because downmixing is only defined for
// channels 4-8, which we are guaranteeing to work.
//
// Note that the loop starts at "1", so the first parameter is ignored in
// these arrays.
const char* kExpectedDeviceName[] = { NULL,
AlsaPcmOutputStream::kDefaultDevice,
AlsaPcmOutputStream::kDefaultDevice,
AlsaPcmOutputStream::kDefaultDevice,
kSurround40, kSurround50, kSurround51,
kSurround70, kSurround71,
AlsaPcmOutputStream::kDefaultDevice };
bool kExpectedDownmix[] = { false, false, false, false, false, true,
false, false, false, false };
ChannelLayout kExpectedLayouts[] = { CHANNEL_LAYOUT_NONE,
CHANNEL_LAYOUT_MONO,
CHANNEL_LAYOUT_STEREO,
CHANNEL_LAYOUT_SURROUND,
CHANNEL_LAYOUT_4_0,
CHANNEL_LAYOUT_5_0,
CHANNEL_LAYOUT_5_1,
CHANNEL_LAYOUT_7_0,
CHANNEL_LAYOUT_7_1 };
for (int i = 1; i < 9; ++i) {
if (i == 3 || i == 4 || i == 5) // invalid number of channels
continue;
SCOPED_TRACE(base::StringPrintf("Attempting %d Channel", i));
// Hints will only be grabbed for channel numbers that have non-default
// devices associated with them.
if (kExpectedDeviceName[i] != AlsaPcmOutputStream::kDefaultDevice) {
// The DeviceNameHint and DeviceNameFreeHint need to be paired to avoid a
// memory leak.
EXPECT_CALL(mock_alsa_wrapper_, DeviceNameHint(_, _, _))
.WillOnce(DoAll(SetArgumentPointee<2>(&kFakeHints[0]), Return(0)));
EXPECT_CALL(mock_alsa_wrapper_, DeviceNameFreeHint(&kFakeHints[0]))
.Times(1);
}
EXPECT_CALL(mock_alsa_wrapper_,
PcmOpen(_, StrEq(kExpectedDeviceName[i]), _, _))
.WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), Return(0)));
EXPECT_CALL(mock_alsa_wrapper_,
PcmSetParams(kFakeHandle, _, _, i, _, _, _))
.WillOnce(Return(0));
// The parameters are specified by ALSA documentation, and are in constants
// in the implementation files.
EXPECT_CALL(mock_alsa_wrapper_, DeviceNameGetHint(_, StrEq("IOID")))
.WillRepeatedly(Invoke(OutputHint));
EXPECT_CALL(mock_alsa_wrapper_, DeviceNameGetHint(_, StrEq("NAME")))
.WillRepeatedly(Invoke(EchoHint));
AlsaPcmOutputStream* test_stream = CreateStream(kExpectedLayouts[i]);
EXPECT_TRUE(test_stream->AutoSelectDevice(i));
EXPECT_EQ(kExpectedDownmix[i],
static_cast<bool>(test_stream->channel_mixer_));
Mock::VerifyAndClearExpectations(&mock_alsa_wrapper_);
Mock::VerifyAndClearExpectations(mock_manager_.get());
test_stream->Close();
}
}
TEST_F(AlsaPcmOutputStreamTest, AutoSelectDevice_FallbackDevices) {
using std::string;
// If there are problems opening a multi-channel device, it the fallbacks
// operations should be as follows. Assume the multi-channel device name is
// surround50:
//
// 1) Try open "surround50"
// 2) Try open "plug:surround50".
// 3) Try open "default".
// 4) Try open "plug:default".
// 5) Give up trying to open.
//
const string first_try = kSurround50;
const string second_try = string(AlsaPcmOutputStream::kPlugPrefix) +
kSurround50;
const string third_try = AlsaPcmOutputStream::kDefaultDevice;
const string fourth_try = string(AlsaPcmOutputStream::kPlugPrefix) +
AlsaPcmOutputStream::kDefaultDevice;
EXPECT_CALL(mock_alsa_wrapper_, DeviceNameHint(_, _, _))
.WillOnce(DoAll(SetArgumentPointee<2>(&kFakeHints[0]), Return(0)));
EXPECT_CALL(mock_alsa_wrapper_, DeviceNameFreeHint(&kFakeHints[0]))
.Times(1);
EXPECT_CALL(mock_alsa_wrapper_, DeviceNameGetHint(_, StrEq("IOID")))
.WillRepeatedly(Invoke(OutputHint));
EXPECT_CALL(mock_alsa_wrapper_, DeviceNameGetHint(_, StrEq("NAME")))
.WillRepeatedly(Invoke(EchoHint));
EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno))
.WillRepeatedly(Return(kDummyMessage));
InSequence s;
EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(first_try.c_str()), _, _))
.WillOnce(Return(kTestFailedErrno));
EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(second_try.c_str()), _, _))
.WillOnce(Return(kTestFailedErrno));
EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(third_try.c_str()), _, _))
.WillOnce(Return(kTestFailedErrno));
EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(fourth_try.c_str()), _, _))
.WillOnce(Return(kTestFailedErrno));
AlsaPcmOutputStream* test_stream = CreateStream(CHANNEL_LAYOUT_5_0);
EXPECT_FALSE(test_stream->AutoSelectDevice(5));
test_stream->Close();
}
TEST_F(AlsaPcmOutputStreamTest, AutoSelectDevice_HintFail) {
// Should get |kDefaultDevice|, and force a 2-channel downmix on a failure to
// enumerate devices.
EXPECT_CALL(mock_alsa_wrapper_, DeviceNameHint(_, _, _))
.WillRepeatedly(Return(kTestFailedErrno));
EXPECT_CALL(mock_alsa_wrapper_,
PcmOpen(_, StrEq(AlsaPcmOutputStream::kDefaultDevice), _, _))
.WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), Return(0)));
EXPECT_CALL(mock_alsa_wrapper_,
PcmSetParams(kFakeHandle, _, _, 2, _, _, _))
.WillOnce(Return(0));
EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno))
.WillOnce(Return(kDummyMessage));
AlsaPcmOutputStream* test_stream = CreateStream(CHANNEL_LAYOUT_5_0);
EXPECT_TRUE(test_stream->AutoSelectDevice(5));
EXPECT_TRUE(test_stream->channel_mixer_);
test_stream->Close();
}
TEST_F(AlsaPcmOutputStreamTest, BufferPacket_StopStream) {
AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
InitBuffer(test_stream);
test_stream->stop_stream_ = true;
bool source_exhausted;
test_stream->BufferPacket(&source_exhausted);
EXPECT_EQ(0, test_stream->buffer_->forward_bytes());
EXPECT_TRUE(source_exhausted);
test_stream->Close();
}
TEST_F(AlsaPcmOutputStreamTest, ScheduleNextWrite) {
AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
test_stream->TransitionTo(AlsaPcmOutputStream::kIsOpened);
test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying);
InitBuffer(test_stream);
DVLOG(1) << test_stream->state();
EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(_))
.WillOnce(Return(10));
test_stream->ScheduleNextWrite(false);
DVLOG(1) << test_stream->state();
// TODO(sergeyu): Figure out how to check that the task has been added to the
// message loop.
// Cleanup the message queue. Currently ~MessageQueue() doesn't free pending
// tasks unless running on valgrind. The code below is needed to keep
// heapcheck happy.
test_stream->stop_stream_ = true;
DVLOG(1) << test_stream->state();
test_stream->TransitionTo(AlsaPcmOutputStream::kIsClosed);
DVLOG(1) << test_stream->state();
test_stream->Close();
}
TEST_F(AlsaPcmOutputStreamTest, ScheduleNextWrite_StopStream) {
AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
test_stream->TransitionTo(AlsaPcmOutputStream::kIsOpened);
test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying);
InitBuffer(test_stream);
test_stream->stop_stream_ = true;
test_stream->ScheduleNextWrite(true);
// TODO(ajwong): Find a way to test whether or not another task has been
// posted so we can verify that the Alsa code will indeed break the task
// posting loop.
test_stream->TransitionTo(AlsaPcmOutputStream::kIsClosed);
test_stream->Close();
}
} // namespace media