diff options
-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(); +} |