summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authoracolwell@chromium.org <acolwell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-10-21 21:44:49 +0000
committeracolwell@chromium.org <acolwell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-10-21 21:44:49 +0000
commit1a04eb71a548d34aee96b7dde4da34098fa851a2 (patch)
treea06cd0ae5afe551f9343a22917a06a2bccf7df9d /media
parent5ed25b3e6f4a9da2b0dfe7b0787f4715cf27ef2e (diff)
downloadchromium_src-1a04eb71a548d34aee96b7dde4da34098fa851a2.zip
chromium_src-1a04eb71a548d34aee96b7dde4da34098fa851a2.tar.gz
chromium_src-1a04eb71a548d34aee96b7dde4da34098fa851a2.tar.bz2
Improve audio underflow handling.
- Added support for increasing decoded audio queue size. - Added a mechanism to notify the pipeline when the audio underflows. - Delay resuming audio playback until the audio queue has filled up. BUG=92254 TEST=AudioRendererBaseTest.Underflow Review URL: http://codereview.chromium.org/8356022 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@106784 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r--media/base/filters.h14
-rw-r--r--media/base/mock_filters.h7
-rw-r--r--media/base/pipeline_impl.cc18
-rw-r--r--media/base/pipeline_impl.h2
-rw-r--r--media/base/pipeline_impl_unittest.cc8
-rw-r--r--media/filters/audio_renderer_algorithm_base.cc31
-rw-r--r--media/filters/audio_renderer_algorithm_base.h10
-rw-r--r--media/filters/audio_renderer_base.cc71
-rw-r--r--media/filters/audio_renderer_base.h23
-rw-r--r--media/filters/audio_renderer_base_unittest.cc130
10 files changed, 268 insertions, 46 deletions
diff --git a/media/base/filters.h b/media/base/filters.h
index 8873bfc..1624a60 100644
--- a/media/base/filters.h
+++ b/media/base/filters.h
@@ -261,9 +261,14 @@ class MEDIA_EXPORT VideoRenderer : public Filter {
class MEDIA_EXPORT AudioRenderer : public Filter {
public:
// Initialize a AudioRenderer with the given AudioDecoder, executing the
- // callback upon completion.
+ // |init_callback| upon completion. |underflow_callback| is called when the
+ // renderer runs out of data to pass to the audio card during playback.
+ // If the |underflow_callback| is called ResumeAfterUnderflow() must be called
+ // to resume playback. Pause(), Seek(), or Stop() cancels the underflow
+ // condition.
virtual void Initialize(AudioDecoder* decoder,
- const base::Closure& callback) = 0;
+ const base::Closure& init_callback,
+ const base::Closure& underflow_callback) = 0;
// Returns true if this filter has received and processed an end-of-stream
// buffer.
@@ -271,6 +276,11 @@ class MEDIA_EXPORT AudioRenderer : public Filter {
// Sets the output volume.
virtual void SetVolume(float volume) = 0;
+
+ // Resumes playback after underflow occurs.
+ // |buffer_more_audio| is set to true if you want to increase the size of the
+ // decoded audio buffer.
+ virtual void ResumeAfterUnderflow(bool buffer_more_audio) = 0;
};
} // namespace media
diff --git a/media/base/mock_filters.h b/media/base/mock_filters.h
index d2a8509..ee3c3d1 100644
--- a/media/base/mock_filters.h
+++ b/media/base/mock_filters.h
@@ -268,11 +268,14 @@ class MockAudioRenderer : public AudioRenderer {
MOCK_METHOD0(OnAudioRendererDisabled, void());
// AudioRenderer implementation.
- MOCK_METHOD2(Initialize, void(AudioDecoder* decoder,
- const base::Closure& callback));
+ MOCK_METHOD3(Initialize, void(AudioDecoder* decoder,
+ const base::Closure& init_callback,
+ const base::Closure& underflow_callback));
MOCK_METHOD0(HasEnded, bool());
MOCK_METHOD1(SetVolume, void(float volume));
+ MOCK_METHOD1(ResumeAfterUnderflow, void(bool buffer_more_audio));
+
protected:
virtual ~MockAudioRenderer();
diff --git a/media/base/pipeline_impl.cc b/media/base/pipeline_impl.cc
index cebab46..d62b0fc 100644
--- a/media/base/pipeline_impl.cc
+++ b/media/base/pipeline_impl.cc
@@ -1220,7 +1220,9 @@ bool PipelineImpl::InitializeAudioRenderer(
return false;
audio_renderer_->Initialize(
- decoder, base::Bind(&PipelineImpl::OnFilterInitialize, this));
+ decoder,
+ base::Bind(&PipelineImpl::OnFilterInitialize, this),
+ base::Bind(&PipelineImpl::OnAudioUnderflow, this));
return true;
}
@@ -1375,4 +1377,18 @@ void PipelineImpl::OnDemuxerSeekDone(base::TimeDelta seek_timestamp,
done_cb.Run(status);
}
+void PipelineImpl::OnAudioUnderflow() {
+ if (MessageLoop::current() != message_loop_) {
+ message_loop_->PostTask(FROM_HERE, base::Bind(
+ &PipelineImpl::OnAudioUnderflow, this));
+ return;
+ }
+
+ if (state_ != kStarted)
+ return;
+
+ if (audio_renderer_)
+ audio_renderer_->ResumeAfterUnderflow(true);
+}
+
} // namespace media
diff --git a/media/base/pipeline_impl.h b/media/base/pipeline_impl.h
index ef52ac2..f2329f1 100644
--- a/media/base/pipeline_impl.h
+++ b/media/base/pipeline_impl.h
@@ -332,6 +332,8 @@ class MEDIA_EXPORT PipelineImpl : public Pipeline, public FilterHost {
void OnDemuxerSeekDone(base::TimeDelta seek_timestamp,
PipelineStatus status);
+ void OnAudioUnderflow();
+
// Message loop used to execute pipeline tasks.
MessageLoop* message_loop_;
diff --git a/media/base/pipeline_impl_unittest.cc b/media/base/pipeline_impl_unittest.cc
index d6b5068..07f773d 100644
--- a/media/base/pipeline_impl_unittest.cc
+++ b/media/base/pipeline_impl_unittest.cc
@@ -163,13 +163,13 @@ class PipelineImplTest : public ::testing::Test {
void InitializeAudioRenderer(bool disable_after_init_callback = false) {
if (disable_after_init_callback) {
EXPECT_CALL(*mocks_->audio_renderer(),
- Initialize(mocks_->audio_decoder(), _))
- .WillOnce(DoAll(Invoke(&RunFilterCallback),
+ Initialize(mocks_->audio_decoder(), _, _))
+ .WillOnce(DoAll(Invoke(&RunFilterCallback3),
DisableAudioRenderer(mocks_->audio_renderer())));
} else {
EXPECT_CALL(*mocks_->audio_renderer(),
- Initialize(mocks_->audio_decoder(), _))
- .WillOnce(Invoke(&RunFilterCallback));
+ Initialize(mocks_->audio_decoder(), _, _))
+ .WillOnce(Invoke(&RunFilterCallback3));
}
EXPECT_CALL(*mocks_->audio_renderer(), SetPlaybackRate(0.0f));
EXPECT_CALL(*mocks_->audio_renderer(), SetVolume(1.0f));
diff --git a/media/filters/audio_renderer_algorithm_base.cc b/media/filters/audio_renderer_algorithm_base.cc
index 0fd074f..affebf5 100644
--- a/media/filters/audio_renderer_algorithm_base.cc
+++ b/media/filters/audio_renderer_algorithm_base.cc
@@ -12,14 +12,18 @@ namespace media {
// The size in bytes we try to maintain for the |queue_|. Previous usage
// maintained a deque of 16 Buffers, each of size 4Kb. This worked well, so we
// maintain this number of bytes (16 * 4096).
-const uint32 kDefaultMinQueueSizeInBytes = 65536;
+const int kDefaultMinQueueSizeInBytes = 65536;
+const int kDefaultMinQueueSizeInMilliseconds = 372; // ~64kb @ 44.1k stereo
+const int kDefaultMaxQueueSizeInBytes = 4608000; // 3 seconds @ 96kHz 7.1
+const int kDefaultMaxQueueSizeInMilliseconds = 3000;
AudioRendererAlgorithmBase::AudioRendererAlgorithmBase()
: channels_(0),
sample_rate_(0),
sample_bytes_(0),
playback_rate_(0.0f),
- queue_(0, kDefaultMinQueueSizeInBytes) {
+ queue_(0, kDefaultMinQueueSizeInBytes),
+ max_queue_capacity_(kDefaultMaxQueueSizeInBytes) {
}
AudioRendererAlgorithmBase::~AudioRendererAlgorithmBase() {}
@@ -43,6 +47,15 @@ void AudioRendererAlgorithmBase::Initialize(
channels_ = channels;
sample_rate_ = sample_rate;
sample_bytes_ = sample_bits / 8;
+
+ // Update the capacity based on time now that we have the audio format
+ // parameters.
+ queue_.set_forward_capacity(
+ DurationToBytes(kDefaultMinQueueSizeInMilliseconds));
+ max_queue_capacity_ =
+ std::min(kDefaultMaxQueueSizeInBytes,
+ DurationToBytes(kDefaultMaxQueueSizeInMilliseconds));
+
request_read_callback_ = callback;
set_playback_rate(initial_playback_rate);
@@ -82,13 +95,25 @@ bool AudioRendererAlgorithmBase::IsQueueEmpty() {
}
bool AudioRendererAlgorithmBase::IsQueueFull() {
- return (queue_.forward_bytes() >= kDefaultMinQueueSizeInBytes);
+ return (queue_.forward_bytes() >= queue_.forward_capacity());
}
uint32 AudioRendererAlgorithmBase::QueueSize() {
return queue_.forward_bytes();
}
+void AudioRendererAlgorithmBase::IncreaseQueueCapacity() {
+ queue_.set_forward_capacity(
+ std::min(2 * queue_.forward_capacity(), max_queue_capacity_));
+}
+
+int AudioRendererAlgorithmBase::DurationToBytes(
+ int duration_in_milliseconds) const {
+ int64 bytes_per_second = sample_bytes_ * channels_ * sample_rate_;
+ int64 bytes = duration_in_milliseconds * bytes_per_second / 1000;
+ return std::min(bytes, static_cast<int64>(kint32max));
+}
+
void AudioRendererAlgorithmBase::AdvanceInputPosition(uint32 bytes) {
queue_.Seek(bytes);
diff --git a/media/filters/audio_renderer_algorithm_base.h b/media/filters/audio_renderer_algorithm_base.h
index f244fc9..af6381d 100644
--- a/media/filters/audio_renderer_algorithm_base.h
+++ b/media/filters/audio_renderer_algorithm_base.h
@@ -75,6 +75,9 @@ class MEDIA_EXPORT AudioRendererAlgorithmBase {
// Returns the number of bytes left in |queue_|.
virtual uint32 QueueSize();
+ // Increase the capacity of |queue_| if possible.
+ virtual void IncreaseQueueCapacity();
+
protected:
// Advances |queue_|'s internal pointer by |bytes|.
void AdvanceInputPosition(uint32 bytes);
@@ -83,6 +86,10 @@ class MEDIA_EXPORT AudioRendererAlgorithmBase {
// bytes successfully copied.
uint32 CopyFromInput(uint8* dest, uint32 bytes);
+ // Converts a duration in milliseconds to a byte count based on
+ // the current sample rate, channel count, and bytes per sample.
+ int DurationToBytes(int duration_in_milliseconds) const;
+
// Number of audio channels.
virtual int channels();
@@ -107,6 +114,9 @@ class MEDIA_EXPORT AudioRendererAlgorithmBase {
// Queued audio data.
SeekableBuffer queue_;
+ // Largest capacity queue_ can grow to.
+ size_t max_queue_capacity_;
+
DISALLOW_COPY_AND_ASSIGN(AudioRendererAlgorithmBase);
};
diff --git a/media/filters/audio_renderer_base.cc b/media/filters/audio_renderer_base.cc
index f942ed4..2d32d98 100644
--- a/media/filters/audio_renderer_base.cc
+++ b/media/filters/audio_renderer_base.cc
@@ -15,6 +15,10 @@
namespace media {
+// Upper bound on the number of pending AudioDecoder reads.
+// TODO(acolwell): Experiment with reducing this to 1.
+const size_t kMaxPendingReads = 4;
+
AudioRendererBase::AudioRendererBase()
: state_(kUninitialized),
recieved_end_of_stream_(false),
@@ -37,7 +41,7 @@ void AudioRendererBase::Play(const base::Closure& callback) {
void AudioRendererBase::Pause(const base::Closure& callback) {
base::AutoLock auto_lock(lock_);
- DCHECK_EQ(kPlaying, state_);
+ DCHECK(state_ == kPlaying || state_ == kUnderflow || state_ == kRebuffering);
pause_callback_ = callback;
state_ = kPaused;
@@ -81,11 +85,14 @@ void AudioRendererBase::Seek(base::TimeDelta time, const FilterStatusCB& cb) {
}
void AudioRendererBase::Initialize(AudioDecoder* decoder,
- const base::Closure& callback) {
+ const base::Closure& init_callback,
+ const base::Closure& underflow_callback) {
DCHECK(decoder);
- DCHECK(!callback.is_null());
+ DCHECK(!init_callback.is_null());
+ DCHECK(!underflow_callback.is_null());
DCHECK_EQ(kUninitialized, state_);
decoder_ = decoder;
+ underflow_callback_ = underflow_callback;
// Use base::Unretained() as the decoder doesn't need to ref us.
decoder_->set_consume_audio_samples_callback(
@@ -109,13 +116,13 @@ void AudioRendererBase::Initialize(AudioDecoder* decoder,
// Give the subclass an opportunity to initialize itself.
if (!OnInitialize(bits_per_channel, channel_layout, sample_rate)) {
host()->SetError(PIPELINE_ERROR_INITIALIZATION_FAILED);
- callback.Run();
+ init_callback.Run();
return;
}
// Finally, execute the start callback.
state_ = kPaused;
- callback.Run();
+ init_callback.Run();
}
bool AudioRendererBase::HasEnded() {
@@ -127,9 +134,20 @@ bool AudioRendererBase::HasEnded() {
return recieved_end_of_stream_ && rendered_end_of_stream_;
}
+void AudioRendererBase::ResumeAfterUnderflow(bool buffer_more_audio) {
+ base::AutoLock auto_lock(lock_);
+ if (state_ == kUnderflow) {
+ if (buffer_more_audio)
+ algorithm_->IncreaseQueueCapacity();
+
+ state_ = kRebuffering;
+ }
+}
+
void AudioRendererBase::ConsumeAudioSamples(scoped_refptr<Buffer> buffer_in) {
base::AutoLock auto_lock(lock_);
- DCHECK(state_ == kPaused || state_ == kSeeking || state_ == kPlaying);
+ DCHECK(state_ == kPaused || state_ == kSeeking || state_ == kPlaying ||
+ state_ == kUnderflow || state_ == kRebuffering);
DCHECK_GT(pending_reads_, 0u);
--pending_reads_;
@@ -182,6 +200,9 @@ uint32 AudioRendererBase::FillBuffer(uint8* dest,
{
base::AutoLock auto_lock(lock_);
+ if (state_ == kRebuffering && algorithm_->IsQueueFull())
+ state_ = kPlaying;
+
// Mute audio by returning 0 when not playing.
if (state_ != kPlaying) {
// TODO(scherkus): To keep the audio hardware busy we write at most 8k of
@@ -198,13 +219,25 @@ uint32 AudioRendererBase::FillBuffer(uint8* dest,
last_fill_buffer_time = last_fill_buffer_time_;
last_fill_buffer_time_ = base::TimeDelta();
- // Use two conditions to determine the end of playback:
+ // Use three conditions to determine the end of playback:
+ // 1. Algorithm has no audio data. (algorithm_->IsQueueEmpty() == true)
+ // 2. Browser process has no audio data. (buffers_empty == true)
+ // 3. We've recieved an end of stream buffer.
+ // (recieved_end_of_stream_ == true)
+ //
+ // Three conditions determine when an underflow occurs:
// 1. Algorithm has no audio data.
- // 2. Browser process has no audio data.
- if (algorithm_->IsQueueEmpty() && buffers_empty) {
- if (recieved_end_of_stream_ && !rendered_end_of_stream_) {
- rendered_end_of_stream_ = true;
- host()->NotifyEnded();
+ // 2. Currently in the kPlaying state.
+ // 3. Have not received an end of stream buffer.
+ if (algorithm_->IsQueueEmpty()) {
+ if (buffers_empty && recieved_end_of_stream_) {
+ if (!rendered_end_of_stream_) {
+ rendered_end_of_stream_ = true;
+ host()->NotifyEnded();
+ }
+ } else if (state_ == kPlaying && !recieved_end_of_stream_) {
+ state_ = kUnderflow;
+ underflow_callback_.Run();
}
} else {
// Otherwise fill the buffer.
@@ -234,12 +267,14 @@ uint32 AudioRendererBase::FillBuffer(uint8* dest,
void AudioRendererBase::ScheduleRead_Locked() {
lock_.AssertAcquired();
- ++pending_reads_;
- // TODO(jiesun): We use dummy buffer to feed decoder to let decoder to
- // provide buffer pools. In the future, we may want to implement real
- // buffer pool to recycle buffers.
- scoped_refptr<Buffer> buffer;
- decoder_->ProduceAudioSamples(buffer);
+ if (pending_reads_ < kMaxPendingReads) {
+ ++pending_reads_;
+ // TODO(jiesun): We use dummy buffer to feed decoder to let decoder to
+ // provide buffer pools. In the future, we may want to implement real
+ // buffer pool to recycle buffers.
+ scoped_refptr<Buffer> buffer;
+ decoder_->ProduceAudioSamples(buffer);
+ }
}
void AudioRendererBase::SetPlaybackRate(float playback_rate) {
diff --git a/media/filters/audio_renderer_base.h b/media/filters/audio_renderer_base.h
index aedbec7..e4d19cf 100644
--- a/media/filters/audio_renderer_base.h
+++ b/media/filters/audio_renderer_base.h
@@ -33,15 +33,18 @@ class MEDIA_EXPORT AudioRendererBase : public AudioRenderer {
virtual ~AudioRendererBase();
// Filter implementation.
- virtual void Play(const base::Closure& callback);
- virtual void Pause(const base::Closure& callback);
- virtual void Stop(const base::Closure& callback);
+ virtual void Play(const base::Closure& callback) OVERRIDE;
+ virtual void Pause(const base::Closure& callback) OVERRIDE;
+ virtual void Stop(const base::Closure& callback) OVERRIDE;
- virtual void Seek(base::TimeDelta time, const FilterStatusCB& cb);
+ virtual void Seek(base::TimeDelta time, const FilterStatusCB& cb) OVERRIDE;
// AudioRenderer implementation.
- virtual void Initialize(AudioDecoder* decoder, const base::Closure& callback);
- virtual bool HasEnded();
+ virtual void Initialize(AudioDecoder* decoder,
+ const base::Closure& init_callback,
+ const base::Closure& underflow_callback) OVERRIDE;
+ virtual bool HasEnded() OVERRIDE;
+ virtual void ResumeAfterUnderflow(bool buffer_more_audio) OVERRIDE;
protected:
// Subclasses should return true if they were able to initialize, false
@@ -74,6 +77,10 @@ class MEDIA_EXPORT AudioRendererBase : public AudioRenderer {
// should the filled buffer be played. If FillBuffer() is called as the audio
// hardware plays the buffer, then |playback_delay| should be zero.
//
+ // |buffers_empty| is set to true when all the hardware buffers become empty.
+ // This is an indication that all the data written to the device has been
+ // played.
+ //
// Safe to call on any thread.
uint32 FillBuffer(uint8* dest,
uint32 len,
@@ -107,6 +114,8 @@ class MEDIA_EXPORT AudioRendererBase : public AudioRenderer {
kPlaying,
kStopped,
kError,
+ kUnderflow,
+ kRebuffering,
};
State state_;
@@ -129,6 +138,8 @@ class MEDIA_EXPORT AudioRendererBase : public AudioRenderer {
base::Closure pause_callback_;
FilterStatusCB seek_cb_;
+ base::Closure underflow_callback_;
+
base::TimeDelta seek_timestamp_;
DISALLOW_COPY_AND_ASSIGN(AudioRendererBase);
diff --git a/media/filters/audio_renderer_base_unittest.cc b/media/filters/audio_renderer_base_unittest.cc
index cfa0850..76b1915 100644
--- a/media/filters/audio_renderer_base_unittest.cc
+++ b/media/filters/audio_renderer_base_unittest.cc
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "base/bind.h"
#include "base/stl_util.h"
#include "media/base/data_buffer.h"
#include "media/base/mock_callback.h"
@@ -12,6 +13,7 @@
#include "testing/gtest/include/gtest/gtest_prod.h"
using ::testing::_;
+using ::testing::AnyNumber;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::NotNull;
@@ -39,6 +41,7 @@ class MockAudioRendererBase : public AudioRendererBase {
private:
FRIEND_TEST(AudioRendererBaseTest, OneCompleteReadCycle);
+ FRIEND_TEST(AudioRendererBaseTest, Underflow);
DISALLOW_COPY_AND_ASSIGN(MockAudioRendererBase);
};
@@ -63,6 +66,13 @@ class AudioRendererBaseTest : public ::testing::Test {
.WillByDefault(Return(CHANNEL_LAYOUT_MONO));
ON_CALL(*decoder_, samples_per_second())
.WillByDefault(Return(44100));
+
+ EXPECT_CALL(*decoder_, bits_per_channel())
+ .Times(AnyNumber());
+ EXPECT_CALL(*decoder_, channel_layout())
+ .Times(AnyNumber());
+ EXPECT_CALL(*decoder_, samples_per_second())
+ .Times(AnyNumber());
}
virtual ~AudioRendererBaseTest() {
@@ -71,6 +81,25 @@ class AudioRendererBaseTest : public ::testing::Test {
renderer_->Stop(NewExpectedClosure());
}
+ MOCK_METHOD0(OnUnderflow, void());
+
+ base::Closure NewUnderflowClosure() {
+ return base::Bind(&AudioRendererBaseTest::OnUnderflow,
+ base::Unretained(this));
+ }
+
+ void WriteUntilNoPendingReads(int data_size, uint8 value,
+ uint32* bytes_buffered) {
+ while (pending_reads_ > 0) {
+ scoped_refptr<DataBuffer> buffer(new DataBuffer(data_size));
+ buffer->SetDataSize(data_size);
+ memset(buffer->GetWritableData(), value, buffer->GetDataSize());
+ --pending_reads_;
+ *bytes_buffered += data_size;
+ decoder_->ConsumeAudioSamplesForTest(buffer);
+ }
+ }
+
protected:
static const size_t kMaxQueueSize;
@@ -103,7 +132,7 @@ TEST_F(AudioRendererBaseTest, Initialize_Failed) {
EXPECT_CALL(host_, SetError(PIPELINE_ERROR_INITIALIZATION_FAILED));
// Initialize, we expect to have no reads.
- renderer_->Initialize(decoder_, NewExpectedClosure());
+ renderer_->Initialize(decoder_, NewExpectedClosure(), NewUnderflowClosure());
EXPECT_EQ(0u, pending_reads_);
}
@@ -115,7 +144,8 @@ TEST_F(AudioRendererBaseTest, Initialize_Successful) {
.WillOnce(Return(true));
// Initialize, we shouldn't have any reads.
- renderer_->Initialize(decoder_, NewExpectedClosure());
+ renderer_->Initialize(decoder_, NewExpectedClosure(), NewUnderflowClosure());
+
EXPECT_EQ(0u, pending_reads_);
// Now seek to trigger prerolling, verifying the callback hasn't been
@@ -143,7 +173,7 @@ TEST_F(AudioRendererBaseTest, OneCompleteReadCycle) {
.WillOnce(Return(true));
// Initialize, we shouldn't have any reads.
- renderer_->Initialize(decoder_, NewExpectedClosure());
+ renderer_->Initialize(decoder_, NewExpectedClosure(), NewUnderflowClosure());
EXPECT_EQ(0u, pending_reads_);
// Now seek to trigger prerolling, verifying the callback hasn't been
@@ -157,13 +187,8 @@ TEST_F(AudioRendererBaseTest, OneCompleteReadCycle) {
// exiting this loop.
const uint32 kDataSize = 1024;
uint32 bytes_buffered = 0;
- while (pending_reads_) {
- scoped_refptr<DataBuffer> buffer(new DataBuffer(kDataSize));
- buffer->SetDataSize(kDataSize);
- decoder_->ConsumeAudioSamplesForTest(buffer);
- --pending_reads_;
- bytes_buffered += kDataSize;
- }
+
+ WriteUntilNoPendingReads(kDataSize, 1, &bytes_buffered);
// Then set the renderer to play state.
renderer_->Play(NewExpectedClosure());
@@ -223,4 +248,89 @@ TEST_F(AudioRendererBaseTest, OneCompleteReadCycle) {
base::TimeDelta(), true));
}
+TEST_F(AudioRendererBaseTest, Underflow) {
+ InSequence s;
+
+ base::TimeDelta playback_delay(base::TimeDelta::FromSeconds(1));
+
+ // Then our subclass will be asked to initialize.
+ EXPECT_CALL(*renderer_, OnInitialize(_, _, _))
+ .WillOnce(Return(true));
+
+ // Initialize, we shouldn't have any reads.
+ renderer_->Initialize(decoder_, NewExpectedClosure(), NewUnderflowClosure());
+ EXPECT_EQ(0u, pending_reads_);
+
+ // Now seek to trigger prerolling, verifying the callback hasn't been
+ // executed yet.
+ EXPECT_CALL(*renderer_, CheckPoint(0));
+ renderer_->Seek(base::TimeDelta(), NewExpectedStatusCB(PIPELINE_OK));
+ EXPECT_EQ(kMaxQueueSize, pending_reads_);
+ renderer_->CheckPoint(0);
+
+ // Now satisfy the read requests. Our callback should be executed after
+ // exiting this loop.
+ const uint32 kDataSize = 1024;
+ uint32 bytes_buffered = 0;
+
+ WriteUntilNoPendingReads(kDataSize, 1, &bytes_buffered);
+
+ uint32 bytes_for_preroll = bytes_buffered;
+
+ // Then set the renderer to play state.
+ renderer_->Play(NewExpectedClosure());
+ renderer_->SetPlaybackRate(1.0f);
+ EXPECT_EQ(1.0f, renderer_->GetPlaybackRate());
+
+ // Consume all of the data passed into the renderer.
+ uint8 buffer[kDataSize];
+ while (bytes_buffered > 0) {
+ EXPECT_EQ(kDataSize,
+ renderer_->FillBuffer(buffer, kDataSize, playback_delay, false));
+ EXPECT_EQ(1, buffer[0]);
+ bytes_buffered -= kDataSize;
+ }
+
+ // Make sure there are read requests pending.
+ EXPECT_GT(pending_reads_, 0u);
+
+ // Verify the next FillBuffer() call triggers calls the underflow callback
+ // since the queue is empty.
+ EXPECT_CALL(*this, OnUnderflow());
+ EXPECT_CALL(*renderer_, CheckPoint(1));
+ EXPECT_EQ(0u, renderer_->FillBuffer(buffer, kDataSize, playback_delay,
+ false));
+ renderer_->CheckPoint(1);
+
+ // Verify that zeroed out buffers are being returned during the underflow.
+ for (int i = 0; i < 5; ++i) {
+ EXPECT_EQ(kDataSize,
+ renderer_->FillBuffer(buffer, kDataSize, playback_delay, false));
+ EXPECT_EQ(0, buffer[0]);
+ }
+
+ renderer_->ResumeAfterUnderflow(false);
+
+ // Verify we are still getting zeroed out buffers since no new data has been
+ // pushed to the renderer.
+ for (int i = 0; i < 5; ++i) {
+ EXPECT_EQ(kDataSize,
+ renderer_->FillBuffer(buffer, kDataSize, playback_delay, false));
+ EXPECT_EQ(0, buffer[0]);
+ }
+
+ // Satisfy all pending read requests.
+ WriteUntilNoPendingReads(kDataSize, 2, &bytes_buffered);
+
+ EXPECT_GE(bytes_buffered, bytes_for_preroll);
+
+ // Verify that we are now getting the new data.
+ while (bytes_buffered > 0) {
+ EXPECT_EQ(kDataSize,
+ renderer_->FillBuffer(buffer, kDataSize, playback_delay, false));
+ EXPECT_EQ(2, buffer[0]);
+ bytes_buffered -= kDataSize;
+ }
+}
+
} // namespace media