diff options
author | dalecurtis@chromium.org <dalecurtis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-11-03 16:51:06 +0000 |
---|---|---|
committer | dalecurtis@chromium.org <dalecurtis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-11-03 16:51:06 +0000 |
commit | 00f4071da99d2183238d368d5055d77d0742551e (patch) | |
tree | d889a91af9d4e24de326a023194c303c3f38d6e1 /media | |
parent | bc66d5d8f7f9973bff1e3201c46d6f3eeb98ebdc (diff) | |
download | chromium_src-00f4071da99d2183238d368d5055d77d0742551e.zip chromium_src-00f4071da99d2183238d368d5055d77d0742551e.tar.gz chromium_src-00f4071da99d2183238d368d5055d77d0742551e.tar.bz2 |
Move OnDecoderInitDone() from decoder to pipeline thread.
We need to ensure |sink_| access is thread safe so every sink implementation
doesn't have to worry about locking and out of order calls.
Failure to do so will lead to dead locks and shutdown crashes.
BUG=158848, 157793
TEST=media_unittests.
Review URL: https://chromiumcodereview.appspot.com/11275087
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@165854 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/filters/audio_renderer_impl.cc | 246 | ||||
-rw-r--r-- | media/filters/audio_renderer_impl.h | 85 | ||||
-rw-r--r-- | media/filters/audio_renderer_impl_unittest.cc | 25 |
3 files changed, 196 insertions, 160 deletions
diff --git a/media/filters/audio_renderer_impl.cc b/media/filters/audio_renderer_impl.cc index 2805ed0..2181d48 100644 --- a/media/filters/audio_renderer_impl.cc +++ b/media/filters/audio_renderer_impl.cc @@ -13,38 +13,42 @@ #include "base/callback_helpers.h" #include "base/command_line.h" #include "base/logging.h" +#include "base/message_loop_proxy.h" #include "media/audio/audio_util.h" +#include "media/base/bind_to_loop.h" #include "media/base/demuxer_stream.h" #include "media/base/media_switches.h" namespace media { AudioRendererImpl::AudioRendererImpl(media::AudioRendererSink* sink) - : state_(kUninitialized), + : sink_(sink), + state_(kUninitialized), pending_read_(false), received_end_of_stream_(false), rendered_end_of_stream_(false), audio_time_buffered_(kNoTimestamp()), current_time_(kNoTimestamp()), - bytes_per_frame_(0), - stopped_(false), - sink_(sink), underflow_disabled_(false), - preroll_aborted_(false) { + preroll_aborted_(false), + actual_frames_per_buffer_(0) { + // We're created on the render thread, but this thread checker is for another. + pipeline_thread_checker_.DetachFromThread(); } void AudioRendererImpl::Play(const base::Closure& callback) { + DCHECK(pipeline_thread_checker_.CalledOnValidThread()); + + float playback_rate = 0; { base::AutoLock auto_lock(lock_); DCHECK_EQ(kPaused, state_); state_ = kPlaying; callback.Run(); + playback_rate = algorithm_->playback_rate(); } - if (stopped_) - return; - - if (GetPlaybackRate() != 0.0f) { + if (playback_rate != 0.0f) { DoPlay(); } else { DoPause(); @@ -52,11 +56,18 @@ void AudioRendererImpl::Play(const base::Closure& callback) { } void AudioRendererImpl::DoPlay() { - earliest_end_time_ = base::Time::Now(); + DCHECK(pipeline_thread_checker_.CalledOnValidThread()); + DCHECK(sink_); + { + base::AutoLock auto_lock(lock_); + earliest_end_time_ = base::Time::Now(); + } sink_->Play(); } void AudioRendererImpl::Pause(const base::Closure& callback) { + DCHECK(pipeline_thread_checker_.CalledOnValidThread()); + { base::AutoLock auto_lock(lock_); DCHECK(state_ == kPlaying || state_ == kUnderflow || @@ -69,30 +80,31 @@ void AudioRendererImpl::Pause(const base::Closure& callback) { base::ResetAndReturn(&pause_cb_).Run(); } - if (stopped_) - return; - DoPause(); } void AudioRendererImpl::DoPause() { + DCHECK(pipeline_thread_checker_.CalledOnValidThread()); + DCHECK(sink_); sink_->Pause(false); } void AudioRendererImpl::Flush(const base::Closure& callback) { + DCHECK(pipeline_thread_checker_.CalledOnValidThread()); decoder_->Reset(callback); } void AudioRendererImpl::Stop(const base::Closure& callback) { + DCHECK(pipeline_thread_checker_.CalledOnValidThread()); DCHECK(!callback.is_null()); + if (sink_) { + sink_->Stop(); + sink_ = NULL; + } + { base::AutoLock auto_lock(lock_); - if (!stopped_) { - sink_->Stop(); - stopped_ = true; - } - state_ = kStopped; algorithm_.reset(NULL); init_cb_.Reset(); @@ -105,30 +117,32 @@ void AudioRendererImpl::Stop(const base::Closure& callback) { void AudioRendererImpl::Preroll(base::TimeDelta time, const PipelineStatusCB& cb) { - base::AutoLock auto_lock(lock_); - DCHECK_EQ(kPaused, state_); - DCHECK(!pending_read_) << "Pending read must complete before seeking"; - DCHECK(pause_cb_.is_null()); - DCHECK(preroll_cb_.is_null()); - state_ = kPrerolling; - preroll_cb_ = cb; - preroll_timestamp_ = time; - - // Throw away everything and schedule our reads. - audio_time_buffered_ = kNoTimestamp(); - current_time_ = kNoTimestamp(); - received_end_of_stream_ = false; - rendered_end_of_stream_ = false; - preroll_aborted_ = false; - - // |algorithm_| will request more reads. - algorithm_->FlushBuffers(); - - if (stopped_) - return; + DCHECK(pipeline_thread_checker_.CalledOnValidThread()); + DCHECK(sink_); + + { + base::AutoLock auto_lock(lock_); + DCHECK_EQ(kPaused, state_); + DCHECK(!pending_read_) << "Pending read must complete before seeking"; + DCHECK(pause_cb_.is_null()); + DCHECK(preroll_cb_.is_null()); + state_ = kPrerolling; + preroll_cb_ = cb; + preroll_timestamp_ = time; + + // Throw away everything and schedule our reads. + audio_time_buffered_ = kNoTimestamp(); + current_time_ = kNoTimestamp(); + received_end_of_stream_ = false; + rendered_end_of_stream_ = false; + preroll_aborted_ = false; + + // |algorithm_| will request more reads. + algorithm_->FlushBuffers(); + earliest_end_time_ = base::Time::Now(); + } // Pause and flush the stream when we preroll to a new location. - earliest_end_time_ = base::Time::Now(); sink_->Pause(true); } @@ -141,7 +155,7 @@ void AudioRendererImpl::Initialize(const scoped_refptr<DemuxerStream>& stream, const base::Closure& ended_cb, const base::Closure& disabled_cb, const PipelineStatusCB& error_cb) { - base::AutoLock auto_lock(lock_); + DCHECK(pipeline_thread_checker_.CalledOnValidThread()); DCHECK(stream); DCHECK(!decoders.empty()); DCHECK_EQ(stream->type(), DemuxerStream::AUDIO); @@ -153,6 +167,7 @@ void AudioRendererImpl::Initialize(const scoped_refptr<DemuxerStream>& stream, DCHECK(!disabled_cb.is_null()); DCHECK(!error_cb.is_null()); DCHECK_EQ(kUninitialized, state_); + DCHECK(sink_); init_cb_ = init_cb; statistics_cb_ = statistics_cb; @@ -169,7 +184,7 @@ void AudioRendererImpl::Initialize(const scoped_refptr<DemuxerStream>& stream, void AudioRendererImpl::InitializeNextDecoder( const scoped_refptr<DemuxerStream>& demuxer_stream, scoped_ptr<AudioDecoderList> decoders) { - lock_.AssertAcquired(); + DCHECK(pipeline_thread_checker_.CalledOnValidThread()); DCHECK(!decoders->empty()); scoped_refptr<AudioDecoder> decoder = decoders->front(); @@ -177,13 +192,10 @@ void AudioRendererImpl::InitializeNextDecoder( DCHECK(decoder); decoder_ = decoder; - - base::AutoUnlock auto_unlock(lock_); decoder->Initialize( - demuxer_stream, - base::Bind(&AudioRendererImpl::OnDecoderInitDone, this, - demuxer_stream, - base::Passed(&decoders)), + demuxer_stream, BindToLoop(base::MessageLoopProxy::current(), base::Bind( + &AudioRendererImpl::OnDecoderInitDone, this, demuxer_stream, + base::Passed(&decoders))), statistics_cb_); } @@ -191,10 +203,10 @@ void AudioRendererImpl::OnDecoderInitDone( const scoped_refptr<DemuxerStream>& demuxer_stream, scoped_ptr<AudioDecoderList> decoders, PipelineStatus status) { - base::AutoLock auto_lock(lock_); + DCHECK(pipeline_thread_checker_.CalledOnValidThread()); if (state_ == kStopped) { - DCHECK(stopped_); + DCHECK(!sink_); return; } @@ -215,8 +227,6 @@ void AudioRendererImpl::OnDecoderInitDone( int channels = ChannelLayoutToChannelCount(channel_layout); int bits_per_channel = decoder_->bits_per_channel(); int sample_rate = decoder_->samples_per_second(); - // TODO(vrk): Add method to AudioDecoder to compute bytes per frame. - bytes_per_frame_ = channels * bits_per_channel / 8; algorithm_.reset(new AudioRendererAlgorithm()); if (!algorithm_->ValidateConfig(channels, sample_rate, bits_per_channel)) { @@ -267,14 +277,16 @@ void AudioRendererImpl::OnDecoderInitDone( audio_parameters_ = AudioParameters( format, channel_layout, sample_rate, bits_per_channel, buffer_size); + state_ = kPaused; + sink_->Initialize(audio_parameters_, this); sink_->Start(); - state_ = kPaused; base::ResetAndReturn(&init_cb_).Run(PIPELINE_OK); } void AudioRendererImpl::ResumeAfterUnderflow(bool buffer_more_audio) { + DCHECK(pipeline_thread_checker_.CalledOnValidThread()); base::AutoLock auto_lock(lock_); if (state_ == kUnderflow) { // The "&& preroll_aborted_" is a hack. If preroll is aborted, then we @@ -291,8 +303,8 @@ void AudioRendererImpl::ResumeAfterUnderflow(bool buffer_more_audio) { } void AudioRendererImpl::SetVolume(float volume) { - if (stopped_) - return; + DCHECK(pipeline_thread_checker_.CalledOnValidThread()); + DCHECK(sink_); sink_->SetVolume(volume); } @@ -376,29 +388,25 @@ void AudioRendererImpl::ScheduleRead_Locked() { } void AudioRendererImpl::SetPlaybackRate(float playback_rate) { + DCHECK(pipeline_thread_checker_.CalledOnValidThread()); DCHECK_LE(0.0f, playback_rate); + DCHECK(sink_); - if (!stopped_) { - // We have two cases here: - // Play: GetPlaybackRate() == 0.0 && playback_rate != 0.0 - // Pause: GetPlaybackRate() != 0.0 && playback_rate == 0.0 - if (GetPlaybackRate() == 0.0f && playback_rate != 0.0f) { - DoPlay(); - } else if (GetPlaybackRate() != 0.0f && playback_rate == 0.0f) { - // Pause is easy, we can always pause. - DoPause(); - } + // We have two cases here: + // Play: current_playback_rate == 0.0 && playback_rate != 0.0 + // Pause: current_playback_rate != 0.0 && playback_rate == 0.0 + float current_playback_rate = algorithm_->playback_rate(); + if (current_playback_rate == 0.0f && playback_rate != 0.0f) { + DoPlay(); + } else if (current_playback_rate != 0.0f && playback_rate == 0.0f) { + // Pause is easy, we can always pause. + DoPause(); } base::AutoLock auto_lock(lock_); algorithm_->SetPlaybackRate(playback_rate); } -float AudioRendererImpl::GetPlaybackRate() { - base::AutoLock auto_lock(lock_); - return algorithm_->playback_rate(); -} - bool AudioRendererImpl::IsBeforePrerollTime( const scoped_refptr<Buffer>& buffer) { return (state_ == kPrerolling) && buffer && !buffer->IsEndOfStream() && @@ -407,43 +415,27 @@ bool AudioRendererImpl::IsBeforePrerollTime( int AudioRendererImpl::Render(AudioBus* audio_bus, int audio_delay_milliseconds) { - if (stopped_ || GetPlaybackRate() == 0.0f) { - // Output silence if stopped. - audio_bus->Zero(); - return 0; - } - - // Adjust the playback delay. - base::TimeDelta request_delay = - base::TimeDelta::FromMilliseconds(audio_delay_milliseconds); - - // Finally we need to adjust the delay according to playback rate. - if (GetPlaybackRate() != 1.0f) { - request_delay = base::TimeDelta::FromMicroseconds( - static_cast<int64>(ceil(request_delay.InMicroseconds() * - GetPlaybackRate()))); + if (actual_frames_per_buffer_ != audio_bus->frames()) { + audio_buffer_.reset( + new uint8[audio_bus->frames() * audio_parameters_.GetBytesPerFrame()]); + actual_frames_per_buffer_ = audio_bus->frames(); } - int bytes_per_frame = audio_parameters_.GetBytesPerFrame(); - - const int buf_size = audio_bus->frames() * bytes_per_frame; - scoped_array<uint8> buf(new uint8[buf_size]); - - int frames_filled = FillBuffer(buf.get(), audio_bus->frames(), request_delay); - int bytes_filled = frames_filled * bytes_per_frame; - DCHECK_LE(bytes_filled, buf_size); - UpdateEarliestEndTime(bytes_filled, request_delay, base::Time::Now()); + int frames_filled = FillBuffer( + audio_buffer_.get(), audio_bus->frames(), audio_delay_milliseconds); + DCHECK_LE(frames_filled, actual_frames_per_buffer_); // Deinterleave audio data into the output bus. audio_bus->FromInterleaved( - buf.get(), frames_filled, audio_parameters_.bits_per_sample() / 8); + audio_buffer_.get(), frames_filled, + audio_parameters_.bits_per_sample() / 8); return frames_filled; } uint32 AudioRendererImpl::FillBuffer(uint8* dest, uint32 requested_frames, - const base::TimeDelta& playback_delay) { + int audio_delay_milliseconds) { base::TimeDelta current_time = kNoTimestamp(); base::TimeDelta max_time = kNoTimestamp(); @@ -452,6 +444,22 @@ uint32 AudioRendererImpl::FillBuffer(uint8* dest, { base::AutoLock auto_lock(lock_); + // Ensure Stop() hasn't destroyed our |algorithm_| on the pipeline thread. + if (!algorithm_) + return 0; + + float playback_rate = algorithm_->playback_rate(); + if (playback_rate == 0.0f) + return 0; + + // Adjust the delay according to playback rate. + base::TimeDelta playback_delay = + base::TimeDelta::FromMilliseconds(audio_delay_milliseconds); + if (playback_rate != 1.0f) { + playback_delay = base::TimeDelta::FromMicroseconds(static_cast<int64>( + ceil(playback_delay.InMicroseconds() * playback_rate))); + } + if (state_ == kRebuffering && algorithm_->IsQueueFull()) state_ = kPlaying; @@ -464,10 +472,10 @@ uint32 AudioRendererImpl::FillBuffer(uint8* dest, // // This should get handled by the subclass http://crbug.com/106600 const uint32 kZeroLength = 8192; - size_t zeros_to_write = - std::min(kZeroLength, requested_frames * bytes_per_frame_); + size_t zeros_to_write = std::min( + kZeroLength, requested_frames * audio_parameters_.GetBytesPerFrame()); memset(dest, 0, zeros_to_write); - return zeros_to_write / bytes_per_frame_; + return zeros_to_write / audio_parameters_.GetBytesPerFrame(); } // We use the following conditions to determine end of playback: @@ -533,6 +541,9 @@ uint32 AudioRendererImpl::FillBuffer(uint8* dest, // buffered audio data. Update the new amount of time buffered. max_time = algorithm_->GetTime(); audio_time_buffered_ = max_time; + + UpdateEarliestEndTime_Locked( + frames_written, playback_rate, playback_delay, base::Time::Now()); } if (current_time != kNoTimestamp() && max_time != kNoTimestamp()) { @@ -545,28 +556,25 @@ uint32 AudioRendererImpl::FillBuffer(uint8* dest, return frames_written; } -void AudioRendererImpl::UpdateEarliestEndTime(int bytes_filled, - base::TimeDelta request_delay, - base::Time time_now) { - if (bytes_filled != 0) { - base::TimeDelta predicted_play_time = ConvertToDuration(bytes_filled); - float playback_rate = GetPlaybackRate(); - if (playback_rate != 1.0f) { - predicted_play_time = base::TimeDelta::FromMicroseconds( - static_cast<int64>(ceil(predicted_play_time.InMicroseconds() * - playback_rate))); - } - earliest_end_time_ = - std::max(earliest_end_time_, - time_now + request_delay + predicted_play_time); +void AudioRendererImpl::UpdateEarliestEndTime_Locked( + int frames_filled, float playback_rate, base::TimeDelta playback_delay, + base::Time time_now) { + if (frames_filled <= 0) + return; + + base::TimeDelta predicted_play_time = base::TimeDelta::FromMicroseconds( + static_cast<float>(frames_filled) * base::Time::kMicrosecondsPerSecond / + audio_parameters_.sample_rate()); + + if (playback_rate != 1.0f) { + predicted_play_time = base::TimeDelta::FromMicroseconds( + static_cast<int64>(ceil(predicted_play_time.InMicroseconds() * + playback_rate))); } -} -base::TimeDelta AudioRendererImpl::ConvertToDuration(int bytes) { - int bytes_per_second = audio_parameters_.GetBytesPerSecond(); - CHECK(bytes_per_second); - return base::TimeDelta::FromMicroseconds( - base::Time::kMicrosecondsPerSecond * bytes / bytes_per_second); + lock_.AssertAcquired(); + earliest_end_time_ = std::max( + earliest_end_time_, time_now + playback_delay + predicted_play_time); } void AudioRendererImpl::OnRenderError() { diff --git a/media/filters/audio_renderer_impl.h b/media/filters/audio_renderer_impl.h index 47a498b..5dffac4 100644 --- a/media/filters/audio_renderer_impl.h +++ b/media/filters/audio_renderer_impl.h @@ -23,6 +23,7 @@ #include <deque> #include "base/synchronization/lock.h" +#include "base/threading/thread_checker.h" #include "media/base/audio_decoder.h" #include "media/base/audio_renderer.h" #include "media/base/audio_renderer_sink.h" @@ -96,31 +97,25 @@ class MEDIA_EXPORT AudioRendererImpl // not called at the same rate as audio samples are played, then the reported // timestamp in the pipeline will be ahead of the actual audio playback. In // this case |playback_delay| should be used to indicate when in the future - // should the filled buffer be played. If FillBuffer() is called as the audio - // hardware plays the buffer, then |playback_delay| should be zero. + // should the filled buffer be played. // // Safe to call on any thread. uint32 FillBuffer(uint8* dest, uint32 requested_frames, - const base::TimeDelta& playback_delay); - - // Get the playback rate of |algorithm_|. - float GetPlaybackRate(); - - // Convert number of bytes to duration of time using information about the - // number of channels, sample rate and sample bits. - base::TimeDelta ConvertToDuration(int bytes); + int audio_delay_milliseconds); // Estimate earliest time when current buffer can stop playing. - void UpdateEarliestEndTime(int bytes_filled, - base::TimeDelta request_delay, - base::Time time_now); + void UpdateEarliestEndTime_Locked(int frames_filled, + float playback_rate, + base::TimeDelta playback_delay, + base::Time time_now); // Methods called on pipeline thread ---------------------------------------- void DoPlay(); void DoPause(); - // media::AudioRendererSink::RenderCallback implementation. + // media::AudioRendererSink::RenderCallback implementation. Called on the + // AudioDevice thread. virtual int Render(AudioBus* audio_bus, int audio_delay_milliseconds) OVERRIDE; virtual void OnRenderError() OVERRIDE; @@ -151,11 +146,40 @@ class MEDIA_EXPORT AudioRendererImpl // Audio decoder. scoped_refptr<AudioDecoder> decoder_; - // Algorithm for scaling audio. - scoped_ptr<AudioRendererAlgorithm> algorithm_; + // The sink (destination) for rendered audio. |sink_| must only be accessed + // on the pipeline thread (verify with |pipeline_thread_checker_|). |sink_| + // must never be called under |lock_| or the 3-way thread bridge between the + // audio, pipeline, and decoder threads may deadlock. + scoped_refptr<media::AudioRendererSink> sink_; + + // Ensures certain methods are always called on the pipeline thread. + base::ThreadChecker pipeline_thread_checker_; + + // AudioParameters constructed during Initialize() based on |decoder_|. + AudioParameters audio_parameters_; + + // Callbacks provided during Initialize(). + PipelineStatusCB init_cb_; + StatisticsCB statistics_cb_; + base::Closure underflow_cb_; + TimeCB time_cb_; + base::Closure ended_cb_; + base::Closure disabled_cb_; + PipelineStatusCB error_cb_; + // Callback provided to Pause(). + base::Closure pause_cb_; + + // Callback provided to Preroll(). + PipelineStatusCB preroll_cb_; + + // After Initialize() has completed, all variables below must be accessed + // under |lock_|. ------------------------------------------------------------ base::Lock lock_; + // Algorithm for scaling audio. + scoped_ptr<AudioRendererAlgorithm> algorithm_; + // Simple state tracking variable. enum State { kUninitialized, @@ -180,29 +204,8 @@ class MEDIA_EXPORT AudioRendererImpl base::TimeDelta audio_time_buffered_; base::TimeDelta current_time_; - PipelineStatusCB init_cb_; - StatisticsCB statistics_cb_; - - // Filter callbacks. - base::Closure pause_cb_; - PipelineStatusCB preroll_cb_; - - base::Closure underflow_cb_; - TimeCB time_cb_; - base::Closure ended_cb_; - base::Closure disabled_cb_; - PipelineStatusCB error_cb_; - base::TimeDelta preroll_timestamp_; - uint32 bytes_per_frame_; - - // A flag that indicates this filter is called to stop. - bool stopped_; - - // The sink (destination) for rendered audio. - scoped_refptr<media::AudioRendererSink> sink_; - // We're supposed to know amount of audio data OS or hardware buffered, but // that is not always so -- on my Linux box // AudioBuffersState::hardware_delay_bytes never reaches 0. @@ -219,14 +222,18 @@ class MEDIA_EXPORT AudioRendererImpl // than nothing. base::Time earliest_end_time_; - AudioParameters audio_parameters_; - bool underflow_disabled_; // True if the renderer receives a buffer with kAborted status during preroll, // false otherwise. This flag is cleared on the next Preroll() call. bool preroll_aborted_; + // End variables which must be accessed under |lock_|. ---------------------- + + // Variables used only on the audio thread. --------------------------------- + int actual_frames_per_buffer_; + scoped_array<uint8> audio_buffer_; + DISALLOW_COPY_AND_ASSIGN(AudioRendererImpl); }; diff --git a/media/filters/audio_renderer_impl_unittest.cc b/media/filters/audio_renderer_impl_unittest.cc index fe901f0..dff1733 100644 --- a/media/filters/audio_renderer_impl_unittest.cc +++ b/media/filters/audio_renderer_impl_unittest.cc @@ -5,6 +5,7 @@ #include "base/bind.h" #include "base/callback_helpers.h" #include "base/gtest_prod_util.h" +#include "base/message_loop.h" #include "base/stl_util.h" #include "media/base/data_buffer.h" #include "media/base/mock_audio_renderer_sink.h" @@ -58,6 +59,7 @@ class AudioRendererImplTest : public ::testing::Test { } virtual ~AudioRendererImplTest() { + message_loop_.RunUntilIdle(); renderer_->Stop(NewExpectedClosure()); } @@ -101,6 +103,7 @@ class AudioRendererImplTest : public ::testing::Test { .WillOnce(RunPipelineStatusCB1(PIPELINE_OK)); InitializeWithStatus(PIPELINE_OK); + message_loop_.RunUntilIdle(); } void InitializeWithStatus(PipelineStatus expected) { @@ -127,6 +130,7 @@ class AudioRendererImplTest : public ::testing::Test { EXPECT_CALL(*decoder_, Read(_)); renderer_->Preroll(base::TimeDelta(), NewPrerollCB()); EXPECT_CALL(*this, OnPrerollComplete(PIPELINE_OK)); + message_loop_.RunUntilIdle(); DeliverRemainingAudio(); } @@ -193,7 +197,7 @@ class AudioRendererImplTest : public ::testing::Test { ChannelLayoutToChannelCount(decoder_->channel_layout()); uint32 requested_frames = size / bytes_per_frame; uint32 frames_read = renderer_->FillBuffer( - buffer.get(), requested_frames, base::TimeDelta()); + buffer.get(), requested_frames, 0); if (frames_read > 0 && muted) { *muted = (buffer[0] == kMutedAudio); @@ -229,6 +233,7 @@ class AudioRendererImplTest : public ::testing::Test { AudioRendererImpl::AudioDecoderList decoders_; AudioDecoder::ReadCB read_cb_; base::TimeDelta next_timestamp_; + MessageLoop message_loop_; private: void SaveReadCallback(const AudioDecoder::ReadCB& callback) { @@ -301,8 +306,20 @@ TEST_F(AudioRendererImplTest, EndOfStream) { Play(); // Drain internal buffer, we should have a pending read. + int audio_bytes_filled = bytes_buffered(); EXPECT_CALL(*decoder_, Read(_)); - EXPECT_TRUE(ConsumeBufferedData(bytes_buffered(), NULL)); + EXPECT_TRUE(ConsumeBufferedData(audio_bytes_filled, NULL)); + + // Check and clear |earliest_end_time_| so the ended event fires on the next + // ConsumeBufferedData() call. + base::TimeDelta audio_play_time = base::TimeDelta::FromMicroseconds( + audio_bytes_filled * base::Time::kMicrosecondsPerSecond / + static_cast<float>(renderer_->audio_parameters_.GetBytesPerSecond())); + base::TimeDelta time_until_ended = + renderer_->earliest_end_time_ - base::Time::Now(); + EXPECT_TRUE(time_until_ended > base::TimeDelta()); + EXPECT_TRUE(time_until_ended <= audio_play_time); + renderer_->earliest_end_time_ = base::Time(); // Fulfill the read with an end-of-stream packet, we shouldn't report ended // nor have a read until we drain the internal buffer. @@ -390,6 +407,10 @@ TEST_F(AudioRendererImplTest, Underflow_EndOfStream) { DeliverEndOfStream(); EXPECT_CALL(*this, OnEnded()); + // Clear |earliest_end_time_| so ended fires on the next ConsumeBufferedData() + // call. + renderer_->earliest_end_time_ = base::Time(); + EXPECT_FALSE(ConsumeBufferedData(kDataSize, &muted)); EXPECT_FALSE(muted); } |