// Copyright 2013 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/strings/stringprintf.h" #include "media/audio/alsa/alsa_output.h" #include "media/audio/alsa/alsa_wrapper.h" #include "media/audio/alsa/audio_manager_alsa.h" #include "media/audio/fake_audio_log_factory.h" #include "media/audio/mock_audio_source_callback.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 MockAudioManagerAlsa : public AudioManagerAlsa { public: MockAudioManagerAlsa() : AudioManagerAlsa(&fake_audio_log_factory_) {} MOCK_METHOD0(Init, void()); MOCK_METHOD0(HasAudioOutputDevices, bool()); MOCK_METHOD0(HasAudioInputDevices, bool()); MOCK_METHOD1(MakeLinearOutputStream, AudioOutputStream*( const AudioParameters& params)); MOCK_METHOD2(MakeLowLatencyOutputStream, AudioOutputStream*( const AudioParameters& params, const std::string& device_id)); 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 task runner. virtual scoped_refptr GetTaskRunner() OVERRIDE { return base::MessageLoop::current()->message_loop_proxy(); } private: FakeAudioLogFactory fake_audio_log_factory_; }; class AlsaPcmOutputStreamTest : public testing::Test { protected: AlsaPcmOutputStreamTest() { mock_manager_.reset(new StrictMock()); } 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(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_->set_data_size(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 mock_alsa_wrapper_; scoped_ptr > mock_manager_; base::MessageLoop message_loop_; scoped_refptr 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(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(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 = AlsaPcmOutputStream::FramesToTimeDelta( kOverMinLatencyPacketSize * 2, kTestSampleRate).InMicroseconds(); 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 = AlsaPcmOutputStream::FramesToTimeDelta( 2 * kTestFramesPerPacket, kTestSampleRate).InMicroseconds(); // 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_->data_size() / kTestBytesPerFrame / 2 - 1; EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle)) .WillOnce(Return(written)); EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(kFakeHandle, packet_->data(), _)) .WillOnce(Return(written)); test_stream->WritePacket(); ASSERT_EQ(test_stream->buffer_->forward_bytes(), packet_->data_size() - written * kTestBytesPerFrame); // Write the rest. EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle)) .WillOnce(Return(kTestFramesPerPacket - written)); EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(kFakeHandle, packet_->data() + written * kTestBytesPerFrame, _)) .WillOnce(Return(packet_->data_size() / 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_->data_size()); // 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_->data_size()); 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(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