diff options
author | acolwell@chromium.org <acolwell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-21 21:44:49 +0000 |
---|---|---|
committer | acolwell@chromium.org <acolwell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-21 21:44:49 +0000 |
commit | 1a04eb71a548d34aee96b7dde4da34098fa851a2 (patch) | |
tree | a06cd0ae5afe551f9343a22917a06a2bccf7df9d /media | |
parent | 5ed25b3e6f4a9da2b0dfe7b0787f4715cf27ef2e (diff) | |
download | chromium_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.h | 14 | ||||
-rw-r--r-- | media/base/mock_filters.h | 7 | ||||
-rw-r--r-- | media/base/pipeline_impl.cc | 18 | ||||
-rw-r--r-- | media/base/pipeline_impl.h | 2 | ||||
-rw-r--r-- | media/base/pipeline_impl_unittest.cc | 8 | ||||
-rw-r--r-- | media/filters/audio_renderer_algorithm_base.cc | 31 | ||||
-rw-r--r-- | media/filters/audio_renderer_algorithm_base.h | 10 | ||||
-rw-r--r-- | media/filters/audio_renderer_base.cc | 71 | ||||
-rw-r--r-- | media/filters/audio_renderer_base.h | 23 | ||||
-rw-r--r-- | media/filters/audio_renderer_base_unittest.cc | 130 |
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 |