summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--media/audio/mac/audio_output_mac.cc40
-rw-r--r--media/audio/mac/audio_output_mac.h4
-rw-r--r--media/audio/mac/audio_output_mac_unittest.cc58
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();
+}