diff options
author | hclam@chromium.org <hclam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-17 22:28:15 +0000 |
---|---|---|
committer | hclam@chromium.org <hclam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-17 22:28:15 +0000 |
commit | 8a7bcb02f938026aba0b7978818f7ae39688ff1a (patch) | |
tree | bf8501a02c8783a67203c070802870498fd9ff09 /media/audio | |
parent | 21f579df24b83cb11e7211945b83561e0b22da32 (diff) | |
download | chromium_src-8a7bcb02f938026aba0b7978818f7ae39688ff1a.zip chromium_src-8a7bcb02f938026aba0b7978818f7ae39688ff1a.tar.gz chromium_src-8a7bcb02f938026aba0b7978818f7ae39688ff1a.tar.bz2 |
Fixing audio in mac
TEST=media_unittests
BUG=21574
Several problems in mac audio:
1. AudioQueueEnqueueBuffer doesn't accept empty buffers. But
we often hit buffer underrun with various reasons. In
order to simulate the behavior of Windows that empty
buffer would make the callback be called after a short time,
we write a short buffer of silence.
2. Mac audio doesn't provide pending bytes (unplayed buffer
size in audio buffer). This is now fixed by having a
running counter.
3. After Stop() is called, Start() will take a long time for
the audio packet to be played. It is found that
AudioQueueEnqueueBuffer should be called before
AudioQueueStart otherwise we'll hit this problem of long
delay for start.
Review URL: http://codereview.chromium.org/194112
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@26510 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media/audio')
-rw-r--r-- | media/audio/mac/audio_output_mac.cc | 40 | ||||
-rw-r--r-- | media/audio/mac/audio_output_mac.h | 4 | ||||
-rw-r--r-- | media/audio/mac/audio_output_mac_unittest.cc | 58 |
3 files changed, 94 insertions, 8 deletions
diff --git a/media/audio/mac/audio_output_mac.cc b/media/audio/mac/audio_output_mac.cc index f2446b4..7799f159 100644 --- a/media/audio/mac/audio_output_mac.cc +++ b/media/audio/mac/audio_output_mac.cc @@ -39,7 +39,9 @@ PCMQueueOutAudioOutputStream::PCMQueueOutAudioOutputStream( audio_queue_(NULL), buffer_(), source_(NULL), - manager_(manager) { + manager_(manager), + silence_bytes_(0), + pending_bytes_(0) { // We must have a manager. DCHECK(manager_); // A frame is one sample across all channels. In interleaved audio the per @@ -54,6 +56,11 @@ PCMQueueOutAudioOutputStream::PCMQueueOutAudioOutputStream( format_.mFramesPerPacket = 1; format_.mBytesPerPacket = (format_.mBitsPerChannel * channels) / 8; format_.mBytesPerFrame = format_.mBytesPerPacket; + + // Silence buffer has a duration of 6ms to simulate the behavior of Windows. + // This value is choosen by experiments and macs cannot keep up with + // anything less than 6ms. + silence_bytes_ = format_.mBytesPerFrame * sampling_rate * 6 / 1000; } PCMQueueOutAudioOutputStream::~PCMQueueOutAudioOutputStream() { @@ -161,16 +168,30 @@ void PCMQueueOutAudioOutputStream::RenderCallback(void* p_this, AudioSourceCallback* source = audio_stream->source_; if (!source) return; + + // Adjust the number of pending bytes by subtracting the amount played. + audio_stream->pending_bytes_ -= buffer->mAudioDataByteSize; size_t capacity = buffer->mAudioDataBytesCapacity; - // TODO(hclam): Provide pending bytes. size_t filled = source->OnMoreData(audio_stream, buffer->mAudioData, - capacity, 0); + capacity, audio_stream->pending_bytes_); + + // In order to keep the callback running, we need to provide a positive amount + // of data to the audio queue. To simulate the behavior of Windows, we write + // a buffer of silence. + if (!filled) { + CHECK(audio_stream->silence_bytes_ <= static_cast<int>(capacity)); + filled = audio_stream->silence_bytes_; + memset(buffer->mAudioData, 0, filled); + } + if (filled > capacity) { // User probably overran our buffer. audio_stream->HandleError(0); return; } buffer->mAudioDataByteSize = filled; + // Incremnet bytes by amount filled into audio buffer. + audio_stream->pending_bytes_ += filled; if (NULL == queue) return; // Queue the audio data to the audio driver. @@ -189,14 +210,12 @@ void PCMQueueOutAudioOutputStream::RenderCallback(void* p_this, void PCMQueueOutAudioOutputStream::Start(AudioSourceCallback* callback) { DCHECK(callback); - OSStatus err = AudioQueueStart(audio_queue_, NULL); - if (err != noErr) { - HandleError(err); - return; - } + OSStatus err = noErr; source_ = callback; + pending_bytes_ = 0; // Ask the source to pre-fill all our buffers before playing. for(size_t ix = 0; ix != kNumBuffers; ++ix) { + buffer_[ix]->mAudioDataByteSize = 0; RenderCallback(this, NULL, buffer_[ix]); } // Queue the buffers to the audio driver, sounds starts now. @@ -207,5 +226,10 @@ void PCMQueueOutAudioOutputStream::Start(AudioSourceCallback* callback) { return; } } + err = AudioQueueStart(audio_queue_, NULL); + if (err != noErr) { + HandleError(err); + return; + } } diff --git a/media/audio/mac/audio_output_mac.h b/media/audio/mac/audio_output_mac.h index 340e1eb..817f48a 100644 --- a/media/audio/mac/audio_output_mac.h +++ b/media/audio/mac/audio_output_mac.h @@ -56,6 +56,10 @@ class PCMQueueOutAudioOutputStream : public AudioOutputStream { AudioSourceCallback* source_; // Our creator, the audio manager needs to be notified when we close. AudioManagerMac* manager_; + // Number of bytes for making a silence buffer. + int silence_bytes_; + // Number of bytes yet to be played in audio buffer. + int pending_bytes_; DISALLOW_COPY_AND_ASSIGN(PCMQueueOutAudioOutputStream); }; diff --git a/media/audio/mac/audio_output_mac_unittest.cc b/media/audio/mac/audio_output_mac_unittest.cc index d6908cb..e5c8955 100644 --- a/media/audio/mac/audio_output_mac_unittest.cc +++ b/media/audio/mac/audio_output_mac_unittest.cc @@ -5,8 +5,25 @@ #include "base/basictypes.h" #include "media/audio/audio_output.h" #include "media/audio/simple_sources.h" +#include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::NiceMock; +using ::testing::NotNull; +using ::testing::Return; + +class MockAudioSource : public AudioOutputStream::AudioSourceCallback { + public: + MOCK_METHOD4(OnMoreData, size_t(AudioOutputStream* stream, void* dest, + size_t max_size, int pending_bytes)); + MOCK_METHOD1(OnClose, void(AudioOutputStream* stream)); + MOCK_METHOD2(OnError, void(AudioOutputStream* stream, int code)); +}; // Validate that the SineWaveAudioSource writes the expected values for // the FORMAT_16BIT_MONO. @@ -104,3 +121,44 @@ TEST(MacAudioTest, PCMWaveStreamPlay200HzTone22KssMono) { oas->Stop(); oas->Close(); } + +// Custom action to clear a memory buffer. +static void ClearBuffer(AudioOutputStream* strea, void* dest, + size_t max_size, size_t pending_bytes) { + memset(dest, 0, max_size); +} + +TEST(MacAudioTest, PCMWaveStreamPendingBytes) { + AudioManager* audio_man = AudioManager::GetAudioManager(); + ASSERT_TRUE(NULL != audio_man); + if (!audio_man->HasAudioDevices()) + return; + AudioOutputStream* oas = + audio_man->MakeAudioStream(AudioManager::AUDIO_PCM_LINEAR, 1, + AudioManager::kAudioCDSampleRate, 16); + ASSERT_TRUE(NULL != oas); + + NiceMock<MockAudioSource> source; + size_t bytes_100_ms = (AudioManager::kAudioCDSampleRate / 10) * 2; + EXPECT_TRUE(oas->Open(bytes_100_ms)); + + // We expect the amount of pending bytes will reaching |bytes_100_ms| + // because the audio output stream has a double buffer scheme. + // And then we will try to provide zero data so the amount of pending bytes + // will go down and eventually read zero. + InSequence s; + EXPECT_CALL(source, OnMoreData(oas, NotNull(), bytes_100_ms, 0)) + .WillOnce(DoAll(Invoke(&ClearBuffer), Return(bytes_100_ms))); + EXPECT_CALL(source, OnMoreData(oas, NotNull(), bytes_100_ms, bytes_100_ms)) + .WillOnce(DoAll(Invoke(&ClearBuffer), Return(bytes_100_ms))); + EXPECT_CALL(source, OnMoreData(oas, NotNull(), bytes_100_ms, bytes_100_ms)) + .WillOnce(Return(0)); + EXPECT_CALL(source, OnMoreData(oas, NotNull(), bytes_100_ms, _)) + .Times(AnyNumber()) + .WillRepeatedly(Return(0)); + + oas->Start(&source); + usleep(500000); + oas->Stop(); + oas->Close(); +} |