| // 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 |