diff options
author | sergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-05-14 23:16:00 +0000 |
---|---|---|
committer | sergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-05-14 23:16:00 +0000 |
commit | 01c41dc19a7706176785b426dccf97f4e24e7270 (patch) | |
tree | 804bf8e62563b94a435e6b5098855c604561339b /media/audio | |
parent | 2e03945a0cb0d34b784e985af3fc05f6ad59ceff (diff) | |
download | chromium_src-01c41dc19a7706176785b426dccf97f4e24e7270.zip chromium_src-01c41dc19a7706176785b426dccf97f4e24e7270.tar.gz chromium_src-01c41dc19a7706176785b426dccf97f4e24e7270.tar.bz2 |
Removed AlsaPcmOutputStrem::Packet. Fixed bug in AlsaPcmOutputStream::ScheduleNextWrite which would cause high CPU consumption.
BUG=28654
TEST=Audio still works on Linux
Review URL: http://codereview.chromium.org/2008010
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@47333 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media/audio')
-rw-r--r-- | media/audio/linux/alsa_output.cc | 194 | ||||
-rw-r--r-- | media/audio/linux/alsa_output.h | 34 | ||||
-rw-r--r-- | media/audio/linux/alsa_output_unittest.cc | 169 | ||||
-rw-r--r-- | media/audio/linux/alsa_wrapper.cc | 5 | ||||
-rw-r--r-- | media/audio/linux/alsa_wrapper.h | 2 |
5 files changed, 217 insertions, 187 deletions
diff --git a/media/audio/linux/alsa_output.cc b/media/audio/linux/alsa_output.cc index 8212413..3ce6644 100644 --- a/media/audio/linux/alsa_output.cc +++ b/media/audio/linux/alsa_output.cc @@ -84,6 +84,8 @@ #include "media/audio/audio_util.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" // Amount of time to wait if we've exhausted the data source. This is to avoid // busy looping. @@ -376,7 +378,8 @@ void AlsaPcmOutputStream::OpenTask(uint32 packet_size) { DCHECK_EQ(MessageLoop::current(), message_loop_); // Initialize the configuration variables. - frames_per_packet_ = packet_size / bytes_per_frame_; + packet_size_ = packet_size; + frames_per_packet_ = packet_size_ / bytes_per_frame_; // Try to open the device. micros_per_packet_ = @@ -397,9 +400,22 @@ void AlsaPcmOutputStream::OpenTask(uint32 packet_size) { if (playback_handle_ == NULL) { stop_stream_ = true; } else { - packet_.reset(new Packet(packet_size)); - if (should_downmix_) { - bytes_per_output_frame_ = 2 * bytes_per_sample_; + bytes_per_output_frame_ = should_downmix_ ? 2 * bytes_per_sample_ : + bytes_per_frame_; + uint32 output_packet_size = frames_per_packet_ * bytes_per_output_frame_; + buffer_.reset(new media::SeekableBuffer(0, output_packet_size)); + + // Get alsa buffer size. + snd_pcm_uframes_t buffer_size; + snd_pcm_uframes_t period_size; + int error = wrapper_->PcmGetParams(playback_handle_, &buffer_size, + &period_size); + if (error < 0) { + LOG(ERROR) << "Failed to get playback buffer size from ALSA: " + << wrapper_->StrError(error); + alsa_buffer_frames_ = frames_per_packet_; + } else { + alsa_buffer_frames_ = buffer_size; } } } @@ -431,17 +447,7 @@ void AlsaPcmOutputStream::StartTask() { return; } - // Do a best-effort pre-roll to fill the buffer. Use integer rounding to find - // the maximum number of full packets that can fit into the buffer. - // - // TODO(ajwong): Handle EAGAIN. - const uint32 num_preroll = latency_micros_ / micros_per_packet_; - for (uint32 i = 0; i < num_preroll; ++i) { - BufferPacket(packet_.get()); - WritePacket(packet_.get()); - } - - ScheduleNextWrite(packet_.get()); + ScheduleNextWrite(); } void AlsaPcmOutputStream::CloseTask() { @@ -456,67 +462,51 @@ void AlsaPcmOutputStream::CloseTask() { playback_handle_ = NULL; // Release the buffer. - packet_.reset(); + buffer_.reset(); // Signal anything that might already be scheduled to stop. stop_stream_ = true; } -void AlsaPcmOutputStream::BufferPacket(Packet* packet) { +void AlsaPcmOutputStream::BufferPacket() { DCHECK_EQ(MessageLoop::current(), message_loop_); // If stopped, simulate a 0-lengthed packet. if (stop_stream_) { - packet->used = packet->size = 0; + buffer_->Clear(); return; } - // Request more data if we don't have any cached. - if (packet->used >= packet->size) { + // Request more data if we have capacity. + if (buffer_->forward_capacity() > buffer_->forward_bytes()) { // Before making a request to source for data. We need to determine the // delay (in bytes) for the requested data to be played. - snd_pcm_sframes_t delay = 0; - - // Don't query ALSA's delay if we have underrun since it'll be jammed at - // some non-zero value and potentially even negative! - if (wrapper_->PcmState(playback_handle_) != SND_PCM_STATE_XRUN) { - int error = wrapper_->PcmDelay(playback_handle_, &delay); - if (error >= 0) { - // Convert frames to bytes, but watch out for those negatives! - delay = (delay < 0 ? 0 : delay) * bytes_per_output_frame_; - } else { - // Assume a delay of zero and attempt to recover the device. - delay = 0; - error = wrapper_->PcmRecover(playback_handle_, - error, - kPcmRecoverIsSilent); - if (error < 0) { - LOG(ERROR) << "Failed querying delay: " << wrapper_->StrError(error); - } - } - } + snd_pcm_sframes_t delay = buffer_->forward_bytes() * bytes_per_frame_ / + bytes_per_output_frame_ + GetCurrentDelay() * bytes_per_output_frame_; - packet->used = 0; - packet->size = shared_data_.OnMoreData(this, packet->buffer.get(), - packet->capacity, delay); - CHECK(packet->size <= packet->capacity) << "Data source overran buffer."; + media::DataBuffer* packet = new media::DataBuffer(packet_size_); + size_t packet_size = + shared_data_.OnMoreData(this, packet->GetWritableData(), + packet->GetBufferSize(), delay); + CHECK(packet_size <= packet->GetBufferSize()) << + "Data source overran buffer."; // This should not happen, but incase it does, drop any trailing bytes // that aren't large enough to make a frame. Without this, packet writing // may stall because the last few bytes in the packet may never get used by // WritePacket. - DCHECK(packet->size % bytes_per_frame_ == 0); - packet->size = (packet->size / bytes_per_frame_) * bytes_per_frame_; + DCHECK(packet_size % bytes_per_frame_ == 0); + packet_size = (packet_size / bytes_per_frame_) * bytes_per_frame_; if (should_downmix_) { - if (media::FoldChannels(packet->buffer.get(), - packet->size, + if (media::FoldChannels(packet->GetWritableData(), + packet_size, channels_, bytes_per_sample_, shared_data_.volume())) { // Adjust packet size for downmix. - packet->size = - packet->size / bytes_per_frame_ * bytes_per_output_frame_; + packet_size = + packet_size / bytes_per_frame_ * bytes_per_output_frame_; } else { LOG(ERROR) << "Folding failed"; } @@ -526,59 +516,60 @@ void AlsaPcmOutputStream::BufferPacket(Packet* packet) { // Handle channel order for 5.0 audio. if (channels_ == 5) { if (bytes_per_sample_ == 1) { - Swizzle50Layout(reinterpret_cast<uint8*>(packet->buffer.get()), - packet->size); + Swizzle50Layout(packet->GetWritableData(), packet_size); } else if (bytes_per_sample_ == 2) { - Swizzle50Layout(reinterpret_cast<int16*>(packet->buffer.get()), - packet->size); + Swizzle50Layout(packet->GetWritableData(), packet_size); } else if (bytes_per_sample_ == 4) { - Swizzle50Layout(reinterpret_cast<int32*>(packet->buffer.get()), - packet->size); + Swizzle50Layout(packet->GetWritableData(), packet_size); } } // Handle channel order for 5.1 audio. if (channels_ == 6) { if (bytes_per_sample_ == 1) { - Swizzle51Layout(reinterpret_cast<uint8*>(packet->buffer.get()), - packet->size); + Swizzle51Layout(packet->GetWritableData(), packet_size); } else if (bytes_per_sample_ == 2) { - Swizzle51Layout(reinterpret_cast<int16*>(packet->buffer.get()), - packet->size); + Swizzle51Layout(packet->GetWritableData(), packet_size); } else if (bytes_per_sample_ == 4) { - Swizzle51Layout(reinterpret_cast<int32*>(packet->buffer.get()), - packet->size); + Swizzle51Layout(packet->GetWritableData(), packet_size); } } - media::AdjustVolume(packet->buffer.get(), - packet->size, + media::AdjustVolume(packet->GetWritableData(), + packet_size, channels_, bytes_per_sample_, shared_data_.volume()); + + packet->SetDataSize(packet_size); + + // Add the packet to the buffer. + buffer_->Append(packet); } } } -void AlsaPcmOutputStream::WritePacket(Packet* packet) { +void AlsaPcmOutputStream::WritePacket() { DCHECK_EQ(MessageLoop::current(), message_loop_); - CHECK(packet->size % bytes_per_output_frame_ == 0); - // If the device is in error, just eat the bytes. if (stop_stream_) { - packet->used = packet->size; + buffer_->Clear(); return; } - if (packet->used < packet->size) { - char* buffer_pos = packet->buffer.get() + packet->used; - snd_pcm_sframes_t frames = FramesInPacket(*packet, bytes_per_output_frame_); + CHECK_EQ(buffer_->forward_bytes() % bytes_per_output_frame_, 0u); + + const uint8* buffer_data; + size_t buffer_size; + if (buffer_->GetCurrentChunk(&buffer_data, &buffer_size)) { + buffer_size = buffer_size - (buffer_size % bytes_per_output_frame_); + snd_pcm_sframes_t frames = buffer_size / bytes_per_output_frame_; DCHECK_GT(frames, 0); snd_pcm_sframes_t frames_written = - wrapper_->PcmWritei(playback_handle_, buffer_pos, frames); + wrapper_->PcmWritei(playback_handle_, buffer_data, frames); if (frames_written < 0) { // Attempt once to immediately recover from EINTR, // EPIPE (overrun/underrun), ESTRPIPE (stream suspended). WritePacket @@ -599,7 +590,14 @@ void AlsaPcmOutputStream::WritePacket(Packet* packet) { stop_stream_ = true; } } else { - packet->used += frames_written * bytes_per_output_frame_; + if (frames_written > frames) { + LOG(WARNING) + << "snd_pcm_writei() has written more frame that we asked."; + frames_written = frames; + } + + // Seek forward in the buffer after we've written some data to ALSA. + buffer_->Seek(frames_written * bytes_per_output_frame_); } } } @@ -611,25 +609,22 @@ void AlsaPcmOutputStream::WriteTask() { return; } - BufferPacket(packet_.get()); - WritePacket(packet_.get()); + BufferPacket(); + WritePacket(); - ScheduleNextWrite(packet_.get()); + ScheduleNextWrite(); } -void AlsaPcmOutputStream::ScheduleNextWrite(Packet* current_packet) { +void AlsaPcmOutputStream::ScheduleNextWrite() { DCHECK_EQ(MessageLoop::current(), message_loop_); if (stop_stream_) { return; } - // Calculate when we should have enough buffer for another packet of data. - // Make sure to take into consideration down-mixing. - uint32 frames_leftover = - FramesInPacket(*current_packet, bytes_per_output_frame_); - uint32 frames_avail_wanted = - (frames_leftover > 0) ? frames_leftover : frames_per_packet_; + // Next write is scheduled for the moment when half of the buffer is + // available. + uint32 frames_avail_wanted = alsa_buffer_frames_ / 2; uint32 available_frames = GetAvailableFrames(); uint32 next_fill_time_ms = 0; @@ -649,13 +644,10 @@ void AlsaPcmOutputStream::ScheduleNextWrite(Packet* current_packet) { } // Avoid busy looping if the data source is exhausted. - if (current_packet->size == 0) { + if (buffer_->forward_bytes() == 0) { next_fill_time_ms = std::max(next_fill_time_ms, kNoDataSleepMilliseconds); } - // Wake up sooner than should be necessary to avoid stutter. - next_fill_time_ms /= 2; // TODO(fbarchard): Remove this hack. - // Only schedule more reads/writes if we are still in the playing state. if (shared_data_.state() == kIsPlaying) { if (next_fill_time_ms == 0) { @@ -673,11 +665,6 @@ void AlsaPcmOutputStream::ScheduleNextWrite(Packet* current_packet) { } } -uint32 AlsaPcmOutputStream::FramesInPacket(const Packet& packet, - uint32 bytes_per_frame) { - return (packet.size - packet.used) / bytes_per_frame; -} - uint32 AlsaPcmOutputStream::FramesToMicros(uint32 frames, uint32 sample_rate) { return frames * base::Time::kMicrosecondsPerSecond / sample_rate; } @@ -805,6 +792,29 @@ snd_pcm_sframes_t AlsaPcmOutputStream::GetAvailableFrames() { return available_frames; } +snd_pcm_sframes_t AlsaPcmOutputStream::GetCurrentDelay() { + snd_pcm_sframes_t delay = 0; + + // Don't query ALSA's delay if we have underrun since it'll be jammed at + // some non-zero value and potentially even negative! + if (wrapper_->PcmState(playback_handle_) != SND_PCM_STATE_XRUN) { + int error = wrapper_->PcmDelay(playback_handle_, &delay); + if (error < 0) { + // Assume a delay of zero and attempt to recover the device. + delay = 0; + error = wrapper_->PcmRecover(playback_handle_, + error, + kPcmRecoverIsSilent); + if (error < 0) { + LOG(ERROR) << "Failed querying delay: " << wrapper_->StrError(error); + } + } + if (delay < 0) + delay = 0; + } + return delay; +} + snd_pcm_t* AlsaPcmOutputStream::AutoSelectDevice(unsigned int latency) { // For auto-selection: // 1) Attempt to open a device that best matches the number of channels diff --git a/media/audio/linux/alsa_output.h b/media/audio/linux/alsa_output.h index 192186b..da77cf2 100644 --- a/media/audio/linux/alsa_output.h +++ b/media/audio/linux/alsa_output.h @@ -39,6 +39,10 @@ #include "media/audio/audio_output.h" #include "testing/gtest/include/gtest/gtest_prod.h" +namespace media { +class SeekableBuffer; +}; // namespace media + class AlsaWrapper; class AudioManagerLinux; @@ -94,7 +98,7 @@ class AlsaPcmOutputStream : FRIEND_TEST(AlsaPcmOutputStreamTest, BufferPacket_Negative); FRIEND_TEST(AlsaPcmOutputStreamTest, BufferPacket_StopStream); FRIEND_TEST(AlsaPcmOutputStreamTest, BufferPacket_Underrun); - FRIEND_TEST(AlsaPcmOutputStreamTest, BufferPacket_UnfinishedPacket); + FRIEND_TEST(AlsaPcmOutputStreamTest, BufferPacket_FullBuffer); FRIEND_TEST(AlsaPcmOutputStreamTest, ConstructedState); FRIEND_TEST(AlsaPcmOutputStreamTest, LatencyFloor); FRIEND_TEST(AlsaPcmOutputStreamTest, OpenClose); @@ -110,22 +114,6 @@ class AlsaPcmOutputStream : virtual ~AlsaPcmOutputStream(); - // In-memory buffer to hold sound samples before pushing to the sound device. - // TODO(ajwong): There are now about 3 buffer/packet implementations. Factor - // them out. - struct Packet { - explicit Packet(uint32 new_capacity) - : capacity(new_capacity), - size(0), - used(0), - buffer(new char[capacity]) { - } - uint32 capacity; - uint32 size; - uint32 used; - scoped_array<char> buffer; - }; - // Flags indicating the state of the stream. enum InternalState { kInError = 0, @@ -144,13 +132,12 @@ class AlsaPcmOutputStream : // Functions to get another packet from the data source and write it into the // ALSA device. - void BufferPacket(Packet* packet); - void WritePacket(Packet* packet); + void BufferPacket(); + void WritePacket(); void WriteTask(); - void ScheduleNextWrite(Packet* current_packet); + void ScheduleNextWrite(); // Utility functions for talking with the ALSA API. - static uint32 FramesInPacket(const Packet& packet, uint32 bytes_per_frame); static uint32 FramesToMicros(uint32 frames, uint32 sample_rate); static uint32 FramesToMillis(uint32 frames, uint32 sample_rate); std::string FindDeviceForChannels(uint32 channels); @@ -159,6 +146,7 @@ class AlsaPcmOutputStream : uint32 latency); bool CloseDevice(snd_pcm_t* handle); snd_pcm_sframes_t GetAvailableFrames(); + snd_pcm_sframes_t GetCurrentDelay(); // Attempts to find the best matching linux audio device for the given number // of channels. This function will set |device_name_| and |should_downmix_|. @@ -227,8 +215,10 @@ class AlsaPcmOutputStream : std::string device_name_; bool should_downmix_; uint32 latency_micros_; + uint32 packet_size_; uint32 micros_per_packet_; uint32 bytes_per_output_frame_; + uint32 alsa_buffer_frames_; // Flag indicating the code should stop reading from the data source or // writing to the ALSA device. This is set because the device has entered @@ -246,7 +236,7 @@ class AlsaPcmOutputStream : // Handle to the actual PCM playback device. snd_pcm_t* playback_handle_; - scoped_ptr<Packet> packet_; + scoped_ptr<media::SeekableBuffer> buffer_; uint32 frames_per_packet_; // Used to check which message loop is allowed to call the public APIs. diff --git a/media/audio/linux/alsa_output_unittest.cc b/media/audio/linux/alsa_output_unittest.cc index 26237ca..546bd88 100644 --- a/media/audio/linux/alsa_output_unittest.cc +++ b/media/audio/linux/alsa_output_unittest.cc @@ -8,6 +8,8 @@ #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" @@ -46,6 +48,9 @@ class MockAlsaWrapper : public AlsaWrapper { 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)); @@ -76,10 +81,8 @@ class MockAudioManagerLinux : public AudioManagerLinux { class AlsaPcmOutputStreamTest : public testing::Test { protected: - AlsaPcmOutputStreamTest() - : packet_(kTestPacketSize + 1) { + AlsaPcmOutputStreamTest() { test_stream_ = CreateStreamWithChannels(kTestChannels); - packet_.size = kTestPacketSize; } virtual ~AlsaPcmOutputStreamTest() { @@ -107,6 +110,15 @@ class AlsaPcmOutputStreamTest : public testing::Test { 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() { + 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 int kTestChannels; static const int kTestSampleRate; static const int kTestBitsPerSample; @@ -132,7 +144,7 @@ class AlsaPcmOutputStreamTest : public testing::Test { StrictMock<MockAudioManagerLinux> mock_manager_; MessageLoop message_loop_; scoped_refptr<AlsaPcmOutputStream> test_stream_; - AlsaPcmOutputStream::Packet packet_; + scoped_refptr<media::DataBuffer> packet_; private: DISALLOW_COPY_AND_ASSIGN(AlsaPcmOutputStreamTest); @@ -224,6 +236,10 @@ TEST_F(AlsaPcmOutputStreamTest, LatencyFloor) { PcmSetParams(_, _, _, _, _, _, AlsaPcmOutputStream::kMinLatencyMicros)) .WillOnce(Return(0)); + EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _)) + .WillOnce(DoAll(SetArgumentPointee<1>(kTestPacketSize), + SetArgumentPointee<2>(kTestPacketSize / 2), + Return(0))); ASSERT_TRUE(test_stream_->Open(kMinLatencyPacketSize)); message_loop_.RunAllPending(); @@ -255,6 +271,10 @@ TEST_F(AlsaPcmOutputStreamTest, LatencyFloor) { EXPECT_CALL(mock_alsa_wrapper_, PcmSetParams(_, _, _, _, _, _, expected_micros)) .WillOnce(Return(0)); + EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _)) + .WillOnce(DoAll(SetArgumentPointee<1>(kTestPacketSize), + SetArgumentPointee<2>(kTestPacketSize / 2), + Return(0))); ASSERT_TRUE(test_stream_->Open(kOverMinLatencyPacketSize)); message_loop_.RunAllPending(); @@ -294,6 +314,10 @@ TEST_F(AlsaPcmOutputStreamTest, OpenClose) { 1, expected_micros)) .WillOnce(Return(0)); + EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(kFakeHandle, _, _)) + .WillOnce(DoAll(SetArgumentPointee<1>(kTestPacketSize), + SetArgumentPointee<2>(kTestPacketSize/2), + Return(0))); // Open the stream. ASSERT_TRUE(test_stream_->Open(kTestPacketSize)); @@ -303,7 +327,7 @@ TEST_F(AlsaPcmOutputStreamTest, OpenClose) { 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_TRUE(test_stream_->buffer_.get()); EXPECT_FALSE(test_stream_->stop_stream_); // Now close it and test that everything was released. @@ -316,7 +340,7 @@ TEST_F(AlsaPcmOutputStreamTest, OpenClose) { message_loop_.RunAllPending(); EXPECT_TRUE(test_stream_->playback_handle_ == NULL); - EXPECT_FALSE(test_stream_->packet_.get()); + EXPECT_FALSE(test_stream_->buffer_.get()); EXPECT_TRUE(test_stream_->stop_stream_); } @@ -338,7 +362,7 @@ TEST_F(AlsaPcmOutputStreamTest, PcmOpenFailed) { test_stream_->shared_data_.state()); EXPECT_TRUE(test_stream_->stop_stream_); EXPECT_TRUE(test_stream_->playback_handle_ == NULL); - EXPECT_FALSE(test_stream_->packet_.get()); + EXPECT_FALSE(test_stream_->buffer_.get()); // Close the stream since we opened it to make destruction happy. EXPECT_CALL(mock_manager_, ReleaseStream(test_stream_.get())); @@ -372,7 +396,7 @@ TEST_F(AlsaPcmOutputStreamTest, PcmSetParamsFailed) { test_stream_->shared_data_.state()); EXPECT_TRUE(test_stream_->stop_stream_); EXPECT_TRUE(test_stream_->playback_handle_ == NULL); - EXPECT_FALSE(test_stream_->packet_.get()); + EXPECT_FALSE(test_stream_->buffer_.get()); // Close the stream since we opened it to make destruction happy. EXPECT_CALL(mock_manager_, ReleaseStream(test_stream_.get())); @@ -389,6 +413,10 @@ TEST_F(AlsaPcmOutputStreamTest, StartStop) { Return(0))); EXPECT_CALL(mock_alsa_wrapper_, PcmSetParams(_, _, _, _, _, _, _)) .WillOnce(Return(0)); + EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _)) + .WillOnce(DoAll(SetArgumentPointee<1>(kTestPacketSize), + SetArgumentPointee<2>(kTestPacketSize / 2), + Return(0))); // Open the stream. ASSERT_TRUE(test_stream_->Open(kTestPacketSize)); @@ -400,26 +428,11 @@ TEST_F(AlsaPcmOutputStreamTest, StartStop) { EXPECT_CALL(mock_alsa_wrapper_, PcmPrepare(kFakeHandle)) .WillOnce(Return(0)); - // Expect the pre-roll. - MockAudioSourceCallback mock_callback; - EXPECT_CALL(mock_alsa_wrapper_, PcmState(kFakeHandle)) - .Times(2) - .WillRepeatedly(Return(SND_PCM_STATE_RUNNING)); - EXPECT_CALL(mock_alsa_wrapper_, PcmDelay(kFakeHandle, _)) - .Times(2) - .WillRepeatedly(DoAll(SetArgumentPointee<1>(0), Return(0))); - EXPECT_CALL(mock_callback, - OnMoreData(test_stream_.get(), _, kTestPacketSize, 0)) - .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)); + MockAudioSourceCallback mock_callback; test_stream_->Start(&mock_callback); message_loop_.RunAllPending(); @@ -434,46 +447,52 @@ TEST_F(AlsaPcmOutputStreamTest, StartStop) { } TEST_F(AlsaPcmOutputStreamTest, WritePacket_FinishedPacket) { + InitBuffer(); + // 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 empty buffer. + test_stream_->buffer_->Clear(); + test_stream_->WritePacket(); } TEST_F(AlsaPcmOutputStreamTest, WritePacket_NormalPacket) { + InitBuffer(); + // Write a little less than half the data. - EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(_, packet_.buffer.get(), _)) - .WillOnce(Return(packet_.size / kTestBytesPerFrame / 2 - 1)); + int written = packet_->GetDataSize() / kTestBytesPerFrame / 2 - 1; + EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(_, packet_->GetData(), _)) + .WillOnce(Return(written)); - test_stream_->WritePacket(&packet_); + test_stream_->WritePacket(); - ASSERT_EQ(packet_.size / 2 - kTestBytesPerFrame, packet_.used); + ASSERT_EQ(test_stream_->buffer_->forward_bytes(), + packet_->GetDataSize() - written * kTestBytesPerFrame); // 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); + PcmWritei(_, packet_->GetData() + written * kTestBytesPerFrame, + _)) + .WillOnce(Return(packet_->GetDataSize() / kTestBytesPerFrame - written)); + test_stream_->WritePacket(); + EXPECT_EQ(0u, test_stream_->buffer_->forward_bytes()); } TEST_F(AlsaPcmOutputStreamTest, WritePacket_WriteFails) { + InitBuffer(); + // 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)); + .WillOnce(Return(packet_->GetDataSize() / kTestBytesPerFrame / 2 - 1)); - test_stream_->WritePacket(&packet_); + test_stream_->WritePacket(); - ASSERT_EQ(packet_.size / 2 - kTestBytesPerFrame, packet_.used); + ASSERT_EQ(test_stream_->buffer_->forward_bytes(), + packet_->GetDataSize() / 2 + kTestBytesPerFrame); // Fail the next write, and see that stop_stream_ is set. EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(_, _, _)) @@ -482,20 +501,24 @@ TEST_F(AlsaPcmOutputStreamTest, WritePacket_WriteFails) { .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); + test_stream_->WritePacket(); + EXPECT_EQ(test_stream_->buffer_->forward_bytes(), + packet_->GetDataSize() / 2 + kTestBytesPerFrame); EXPECT_TRUE(test_stream_->stop_stream_); } TEST_F(AlsaPcmOutputStreamTest, WritePacket_StopStream) { + InitBuffer(); + // 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_stream_->WritePacket(); + EXPECT_EQ(0u, test_stream_->buffer_->forward_bytes()); } TEST_F(AlsaPcmOutputStreamTest, BufferPacket) { - packet_.used = packet_.size; + InitBuffer(); + test_stream_->buffer_->Clear(); // Return a partially filled packet. MockAudioSourceCallback mock_callback; @@ -504,19 +527,18 @@ TEST_F(AlsaPcmOutputStreamTest, BufferPacket) { EXPECT_CALL(mock_alsa_wrapper_, PcmDelay(_, _)) .WillOnce(DoAll(SetArgumentPointee<1>(1), Return(0))); EXPECT_CALL(mock_callback, - OnMoreData(test_stream_.get(), packet_.buffer.get(), - packet_.capacity, kTestBytesPerFrame)) + OnMoreData(test_stream_.get(), _, _, kTestBytesPerFrame)) .WillOnce(Return(10)); test_stream_->shared_data_.set_source_callback(&mock_callback); - test_stream_->BufferPacket(&packet_); + test_stream_->BufferPacket(); - EXPECT_EQ(0u, packet_.used); - EXPECT_EQ(10u, packet_.size); + EXPECT_EQ(10u, test_stream_->buffer_->forward_bytes()); } TEST_F(AlsaPcmOutputStreamTest, BufferPacket_Negative) { - packet_.used = packet_.size; + InitBuffer(); + test_stream_->buffer_->Clear(); // Simulate where the underrun has occurred right after checking the delay. MockAudioSourceCallback mock_callback; @@ -525,41 +547,38 @@ TEST_F(AlsaPcmOutputStreamTest, BufferPacket_Negative) { EXPECT_CALL(mock_alsa_wrapper_, PcmDelay(_, _)) .WillOnce(DoAll(SetArgumentPointee<1>(-1), Return(0))); EXPECT_CALL(mock_callback, - OnMoreData(test_stream_.get(), packet_.buffer.get(), - packet_.capacity, 0)) + OnMoreData(test_stream_.get(), _, _, 0)) .WillOnce(Return(10)); test_stream_->shared_data_.set_source_callback(&mock_callback); - test_stream_->BufferPacket(&packet_); + test_stream_->BufferPacket(); - EXPECT_EQ(0u, packet_.used); - EXPECT_EQ(10u, packet_.size); + EXPECT_EQ(10u, test_stream_->buffer_->forward_bytes()); } TEST_F(AlsaPcmOutputStreamTest, BufferPacket_Underrun) { - packet_.used = packet_.size; + InitBuffer(); + 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_callback, - OnMoreData(test_stream_.get(), packet_.buffer.get(), - packet_.capacity, 0)) + OnMoreData(test_stream_.get(), _, _, 0)) .WillOnce(Return(10)); test_stream_->shared_data_.set_source_callback(&mock_callback); - test_stream_->BufferPacket(&packet_); + test_stream_->BufferPacket(); - EXPECT_EQ(0u, packet_.used); - EXPECT_EQ(10u, packet_.size); + EXPECT_EQ(10u, test_stream_->buffer_->forward_bytes()); } -TEST_F(AlsaPcmOutputStreamTest, BufferPacket_UnfinishedPacket) { +TEST_F(AlsaPcmOutputStreamTest, BufferPacket_FullBuffer) { + InitBuffer(); // 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_stream_->BufferPacket(); + EXPECT_EQ(kTestPacketSize, test_stream_->buffer_->forward_bytes()); } TEST_F(AlsaPcmOutputStreamTest, AutoSelectDevice_DeviceSelect) { @@ -684,19 +703,21 @@ TEST_F(AlsaPcmOutputStreamTest, AutoSelectDevice_HintFail) { } TEST_F(AlsaPcmOutputStreamTest, BufferPacket_StopStream) { + InitBuffer(); test_stream_->stop_stream_ = true; - test_stream_->BufferPacket(&packet_); - EXPECT_EQ(0u, packet_.used); - EXPECT_EQ(0u, packet_.size); + test_stream_->BufferPacket(); + EXPECT_EQ(0u, test_stream_->buffer_->forward_bytes()); } TEST_F(AlsaPcmOutputStreamTest, ScheduleNextWrite) { test_stream_->shared_data_.TransitionTo(AlsaPcmOutputStream::kIsOpened); test_stream_->shared_data_.TransitionTo(AlsaPcmOutputStream::kIsPlaying); + InitBuffer(); + EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(_)) .WillOnce(Return(10)); - test_stream_->ScheduleNextWrite(&packet_); + test_stream_->ScheduleNextWrite(); test_stream_->shared_data_.TransitionTo(AlsaPcmOutputStream::kIsClosed); } @@ -705,8 +726,10 @@ TEST_F(AlsaPcmOutputStreamTest, ScheduleNextWrite_StopStream) { test_stream_->shared_data_.TransitionTo(AlsaPcmOutputStream::kIsOpened); test_stream_->shared_data_.TransitionTo(AlsaPcmOutputStream::kIsPlaying); + InitBuffer(); + test_stream_->stop_stream_ = true; - test_stream_->ScheduleNextWrite(&packet_); + test_stream_->ScheduleNextWrite(); // 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 diff --git a/media/audio/linux/alsa_wrapper.cc b/media/audio/linux/alsa_wrapper.cc index a028484..a70856a 100644 --- a/media/audio/linux/alsa_wrapper.cc +++ b/media/audio/linux/alsa_wrapper.cc @@ -67,6 +67,11 @@ int AlsaWrapper::PcmSetParams(snd_pcm_t* handle, snd_pcm_format_t format, soft_resample, latency); } +int AlsaWrapper::PcmGetParams(snd_pcm_t* handle, snd_pcm_uframes_t* buffer_size, + snd_pcm_uframes_t* period_size) { + return snd_pcm_get_params(handle, buffer_size, period_size); +} + snd_pcm_sframes_t AlsaWrapper::PcmAvailUpdate(snd_pcm_t* handle) { return snd_pcm_avail_update(handle); } diff --git a/media/audio/linux/alsa_wrapper.h b/media/audio/linux/alsa_wrapper.h index ce045ea..4469687 100644 --- a/media/audio/linux/alsa_wrapper.h +++ b/media/audio/linux/alsa_wrapper.h @@ -33,6 +33,8 @@ class AlsaWrapper { snd_pcm_access_t access, unsigned int channels, unsigned int rate, int soft_resample, unsigned int latency); + virtual int PcmGetParams(snd_pcm_t* handle, snd_pcm_uframes_t* buffer_size, + snd_pcm_uframes_t* period_size); virtual const char* PcmName(snd_pcm_t* handle); virtual snd_pcm_sframes_t PcmAvailUpdate(snd_pcm_t* handle); virtual snd_pcm_state_t PcmState(snd_pcm_t* handle); |