diff options
author | ajwong@chromium.org <ajwong@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-04 21:49:12 +0000 |
---|---|---|
committer | ajwong@chromium.org <ajwong@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-04 21:49:12 +0000 |
commit | 83337270c01c66378d443f5c3d15926cac164747 (patch) | |
tree | a94d2b86aa74a0fa15a5f9404f4abc6c7cc725e4 /media/audio/linux/alsa_output_unittest.cc | |
parent | cdf6cc8f2b5036894359123804d28b92b9cfa9ee (diff) | |
download | chromium_src-83337270c01c66378d443f5c3d15926cac164747.zip chromium_src-83337270c01c66378d443f5c3d15926cac164747.tar.gz chromium_src-83337270c01c66378d443f5c3d15926cac164747.tar.bz2 |
Reimplement the AlsaPcmOutputStream and fix the threading issues.
Changes the threading model from one thread per stream to one shared thread. Also, redoes the alsa reading/buffering logic to assume an asynchronous data source, and respect the different packet sizes. The Alsa device is set to non-blocking now. State transitions are cleaned up, and the threading semantics are reworked. Now linux audio will no longer crash on shutdown, seek, pause, or tab close. This implementation does still leak though. :( The leak will be fixed in another CL.
Review URL: http://codereview.chromium.org/160497
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@22414 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media/audio/linux/alsa_output_unittest.cc')
-rw-r--r-- | media/audio/linux/alsa_output_unittest.cc | 379 |
1 files changed, 379 insertions, 0 deletions
diff --git a/media/audio/linux/alsa_output_unittest.cc b/media/audio/linux/alsa_output_unittest.cc new file mode 100644 index 0000000..b215b3a --- /dev/null +++ b/media/audio/linux/alsa_output_unittest.cc @@ -0,0 +1,379 @@ +// Copyright (c) 2009 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/logging.h" +#include "media/audio/linux/alsa_output.h" +#include "media/audio/linux/alsa_wrapper.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::_; +using testing::DoAll; +using testing::Eq; +using testing::Return; +using testing::SetArgumentPointee; +using testing::StrictMock; +using testing::StrEq; + +class MockAlsaWrapper : public AlsaWrapper { + public: + 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_METHOD3(PcmWritei, snd_pcm_sframes_t(snd_pcm_t* handle, + const 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_METHOD1(PcmName, const char*(snd_pcm_t* handle)); + MOCK_METHOD1(PcmAvailUpdate, snd_pcm_sframes_t (snd_pcm_t* handle)); + + MOCK_METHOD1(StrError, const char*(int errnum)); +}; + +class MockAudioSourceCallback : public AudioOutputStream::AudioSourceCallback { + public: + MOCK_METHOD3(OnMoreData, size_t(AudioOutputStream* stream, + void* dest, size_t max_size)); + MOCK_METHOD1(OnClose, void(AudioOutputStream* stream)); + MOCK_METHOD2(OnError, void(AudioOutputStream* stream, int code)); +}; + +class AlsaPcmOutputStreamTest : public testing::Test { + protected: + AlsaPcmOutputStreamTest() + : packet_(kTestPacketSize + 1) { + test_stream_ = new AlsaPcmOutputStream(kTestDeviceName, + kTestFormat, + kTestChannels, + kTestSampleRate, + kTestBitsPerSample, + &mock_alsa_wrapper_, + &message_loop_); + + packet_.size = kTestPacketSize; + } + + virtual ~AlsaPcmOutputStreamTest() { + test_stream_ = NULL; + } + + static const int kTestChannels; + static const int kTestSampleRate; + static const int kTestBitsPerSample; + static const int kTestBytesPerFrame; + static const AudioManager::Format kTestFormat; + static const char kTestDeviceName[]; + static const char kDummyMessage[]; + static const int kTestFramesPerPacket; + static const size_t kTestPacketSize; + static const int kTestFailedErrno; + static snd_pcm_t* const kFakeHandle; + + StrictMock<MockAlsaWrapper> mock_alsa_wrapper_; + MessageLoop message_loop_; + scoped_refptr<AlsaPcmOutputStream> test_stream_; + AlsaPcmOutputStream::Packet packet_; + + private: + DISALLOW_COPY_AND_ASSIGN(AlsaPcmOutputStreamTest); +}; + +const int AlsaPcmOutputStreamTest::kTestChannels = 2; +const int AlsaPcmOutputStreamTest::kTestSampleRate = + AudioManager::kAudioCDSampleRate; +const int AlsaPcmOutputStreamTest::kTestBitsPerSample = 8; +const int AlsaPcmOutputStreamTest::kTestBytesPerFrame = + AlsaPcmOutputStreamTest::kTestBitsPerSample / 8 * + AlsaPcmOutputStreamTest::kTestChannels; +const AudioManager::Format AlsaPcmOutputStreamTest::kTestFormat = + AudioManager::AUDIO_PCM_LINEAR; +const char AlsaPcmOutputStreamTest::kTestDeviceName[] = "TestDevice"; +const char AlsaPcmOutputStreamTest::kDummyMessage[] = "dummy"; +const int AlsaPcmOutputStreamTest::kTestFramesPerPacket = 100; +const size_t AlsaPcmOutputStreamTest::kTestPacketSize = + AlsaPcmOutputStreamTest::kTestFramesPerPacket * + AlsaPcmOutputStreamTest::kTestBytesPerFrame; +const int AlsaPcmOutputStreamTest::kTestFailedErrno = -EACCES; +snd_pcm_t* const AlsaPcmOutputStreamTest::kFakeHandle = + reinterpret_cast<snd_pcm_t*>(1); + +TEST_F(AlsaPcmOutputStreamTest, ConstructedState) { + EXPECT_EQ(AlsaPcmOutputStream::kCreated, + test_stream_->shared_data_.state()); + + // Only supports 2 channel. + test_stream_ = new AlsaPcmOutputStream(kTestDeviceName, + kTestFormat, + kTestChannels + 1, + kTestSampleRate, + kTestBitsPerSample, + &mock_alsa_wrapper_, + &message_loop_); + EXPECT_EQ(AlsaPcmOutputStream::kInError, + test_stream_->shared_data_.state()); + + // Bad bits per sample. + test_stream_ = new AlsaPcmOutputStream(kTestDeviceName, + kTestFormat, + kTestChannels, + kTestSampleRate, + kTestBitsPerSample - 1, + &mock_alsa_wrapper_, + &message_loop_); + EXPECT_EQ(AlsaPcmOutputStream::kInError, + test_stream_->shared_data_.state()); + + // Bad format. + test_stream_ = new AlsaPcmOutputStream(kTestDeviceName, + AudioManager::AUDIO_PCM_DELTA, + kTestChannels, + kTestSampleRate, + kTestBitsPerSample, + &mock_alsa_wrapper_, + &message_loop_); + EXPECT_EQ(AlsaPcmOutputStream::kInError, + test_stream_->shared_data_.state()); +} + +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_S8, + SND_PCM_ACCESS_RW_INTERLEAVED, + kTestChannels, + kTestSampleRate, + 1, + expected_micros)) + .WillOnce(Return(0)); + + // Open the stream. + ASSERT_TRUE(test_stream_->Open(kTestPacketSize)); + message_loop_.RunAllPending(); + + EXPECT_EQ(AlsaPcmOutputStream::kIsOpened, + test_stream_->shared_data_.state()); + EXPECT_EQ(kFakeHandle, test_stream_->playback_handle_); + EXPECT_EQ(kTestFramesPerPacket, test_stream_->frames_per_packet_); + EXPECT_TRUE(test_stream_->packet_.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)); + test_stream_->Close(); + message_loop_.RunAllPending(); + + EXPECT_EQ(NULL, test_stream_->playback_handle_); + EXPECT_FALSE(test_stream_->packet_.get()); + EXPECT_TRUE(test_stream_->stop_stream_); +} + +TEST_F(AlsaPcmOutputStreamTest, PcmOpenFailed) { + EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _)) + .WillOnce(Return(kTestFailedErrno)); + 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. + ASSERT_FALSE(test_stream_->Open(kTestPacketSize)); + EXPECT_EQ(AlsaPcmOutputStream::kCreated, + test_stream_->shared_data_.state()); +} + +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_, StrError(kTestFailedErrno)) + .WillOnce(Return(kDummyMessage)); + + // If open fails, the stream stays in kCreated because it has effectively had + // no changes. + ASSERT_FALSE(test_stream_->Open(kTestPacketSize)); + EXPECT_EQ(AlsaPcmOutputStream::kCreated, + test_stream_->shared_data_.state()); +} + +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)); + + // Open the stream. + ASSERT_TRUE(test_stream_->Open(kTestPacketSize)); + message_loop_.RunAllPending(); + + // 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_callback, + OnMoreData(test_stream_.get(), _, kTestPacketSize)) + .Times(2) + .WillRepeatedly(Return(kTestPacketSize)); + EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(kFakeHandle, _, _)) + .Times(2) + .WillRepeatedly(Return(kTestPacketSize)); + + // Expect scheduling. + EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle)) + .WillOnce(Return(1)); + + test_stream_->Start(&mock_callback); + message_loop_.RunAllPending(); + + EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle)) + .WillOnce(Return(0)); + test_stream_->Close(); + message_loop_.RunAllPending(); +} + +TEST_F(AlsaPcmOutputStreamTest, WritePacket_FinishedPacket) { + // Nothing should happen. Don't set any expectations and Our strict mocks + // should verify most of this. + + // Test regular used-up packet. + packet_.used = packet_.size; + test_stream_->WritePacket(&packet_); + + // Test empty packet. + packet_.used = packet_.size = 0; + test_stream_->WritePacket(&packet_); +} + +TEST_F(AlsaPcmOutputStreamTest, WritePacket_NormalPacket) { + // Write a little less than half the data. + EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(_, packet_.buffer.get(), _)) + .WillOnce(Return(packet_.size / kTestBytesPerFrame / 2 - 1)); + + test_stream_->WritePacket(&packet_); + + ASSERT_EQ(packet_.size / 2 - kTestBytesPerFrame, packet_.used); + + // Write the rest. + EXPECT_CALL(mock_alsa_wrapper_, + PcmWritei(_, packet_.buffer.get() + packet_.used, _)) + .WillOnce(Return(packet_.size / kTestBytesPerFrame / 2 + 1)); + test_stream_->WritePacket(&packet_); + EXPECT_EQ(packet_.size, packet_.used); +} + +TEST_F(AlsaPcmOutputStreamTest, WritePacket_WriteFails) { + // Fail due to a recoverable error and see that PcmRecover code path + // continues normally. + EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(_, _, _)) + .WillOnce(Return(-EINTR)); + EXPECT_CALL(mock_alsa_wrapper_, PcmRecover(_, _, _)) + .WillOnce(Return(packet_.size / kTestBytesPerFrame / 2 - 1)); + + test_stream_->WritePacket(&packet_); + + ASSERT_EQ(packet_.size / 2 - kTestBytesPerFrame, packet_.used); + + // Fail the next write, and see that stop_stream_ is set. + EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(_, _, _)) + .WillOnce(Return(kTestFailedErrno)); + EXPECT_CALL(mock_alsa_wrapper_, PcmRecover(_, _, _)) + .WillOnce(Return(kTestFailedErrno)); + EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno)) + .WillOnce(Return(kDummyMessage)); + test_stream_->WritePacket(&packet_); + EXPECT_EQ(packet_.size / 2 - kTestBytesPerFrame, packet_.used); + EXPECT_TRUE(test_stream_->stop_stream_); +} + +TEST_F(AlsaPcmOutputStreamTest, WritePacket_StopStream) { + // No expectations set on the strict mock because nothing should be called. + test_stream_->stop_stream_ = true; + test_stream_->WritePacket(&packet_); + EXPECT_EQ(packet_.size, packet_.used); +} + +TEST_F(AlsaPcmOutputStreamTest, BufferPacket) { + packet_.used = packet_.size; + + // Return a partially filled packet. + MockAudioSourceCallback mock_callback; + EXPECT_CALL(mock_callback, + OnMoreData(test_stream_.get(), packet_.buffer.get(), + packet_.capacity)) + .WillOnce(Return(10)); + + test_stream_->source_callback_ = &mock_callback; + test_stream_->BufferPacket(&packet_); + + EXPECT_EQ(0u, packet_.used); + EXPECT_EQ(10u, packet_.size); +} + +TEST_F(AlsaPcmOutputStreamTest, BufferPacket_UnfinishedPacket) { + // No expectations set on the strict mock because nothing should be called. + test_stream_->BufferPacket(&packet_); + EXPECT_EQ(0u, packet_.used); + EXPECT_EQ(kTestPacketSize, packet_.size); +} + +TEST_F(AlsaPcmOutputStreamTest, BufferPacket_StopStream) { + test_stream_->stop_stream_ = true; + test_stream_->BufferPacket(&packet_); + EXPECT_EQ(0u, packet_.used); + EXPECT_EQ(0u, packet_.size); +} + +TEST_F(AlsaPcmOutputStreamTest, ScheduleNextWrite) { + test_stream_->shared_data_.TransitionTo(AlsaPcmOutputStream::kIsOpened); + test_stream_->shared_data_.TransitionTo(AlsaPcmOutputStream::kIsPlaying); + + EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(_)) + .WillOnce(Return(10)); + test_stream_->ScheduleNextWrite(&packet_); + + test_stream_->shared_data_.TransitionTo(AlsaPcmOutputStream::kIsClosed); +} + +TEST_F(AlsaPcmOutputStreamTest, ScheduleNextWrite_StopStream) { + test_stream_->shared_data_.TransitionTo(AlsaPcmOutputStream::kIsOpened); + test_stream_->shared_data_.TransitionTo(AlsaPcmOutputStream::kIsPlaying); + + test_stream_->stop_stream_ = true; + test_stream_->ScheduleNextWrite(&packet_); + + // 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_->shared_data_.TransitionTo(AlsaPcmOutputStream::kIsClosed); +} |