diff options
-rwxr-xr-x | chrome/chrome.gyp | 1 | ||||
-rw-r--r-- | chrome/renderer/audio_message_filter.h | 3 | ||||
-rw-r--r-- | chrome/renderer/media/audio_renderer_impl.cc | 27 | ||||
-rw-r--r-- | chrome/renderer/media/audio_renderer_impl.h | 12 | ||||
-rw-r--r-- | chrome/renderer/media/audio_renderer_impl_unittest.cc | 129 | ||||
-rw-r--r-- | media/filters/audio_renderer_base.cc | 7 |
6 files changed, 177 insertions, 2 deletions
diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index 0f7c302..c81b650 100755 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -4715,6 +4715,7 @@ 'common/worker_thread_ticker_unittest.cc', 'common/zip_unittest.cc', 'renderer/audio_message_filter_unittest.cc', + 'renderer/media/audio_renderer_impl_unittest.cc', 'renderer/extensions/extension_api_client_unittest.cc', 'renderer/extensions/json_schema_unittest.cc', 'renderer/net/render_dns_master_unittest.cc', diff --git a/chrome/renderer/audio_message_filter.h b/chrome/renderer/audio_message_filter.h index 7516a68..9a21046 100644 --- a/chrome/renderer/audio_message_filter.h +++ b/chrome/renderer/audio_message_filter.h @@ -49,6 +49,9 @@ class AudioMessageFilter : public IPC::ChannelProxy::MessageFilter { MessageLoop* message_loop() { return message_loop_; } private: + // For access to |message_loop_|. + friend class AudioRendererImplTest; + FRIEND_TEST(AudioMessageFilterTest, Basic); FRIEND_TEST(AudioMessageFilterTest, Delegates); diff --git a/chrome/renderer/media/audio_renderer_impl.cc b/chrome/renderer/media/audio_renderer_impl.cc index dfca1be..f10ff37 100644 --- a/chrome/renderer/media/audio_renderer_impl.cc +++ b/chrome/renderer/media/audio_renderer_impl.cc @@ -95,6 +95,8 @@ void AudioRendererImpl::OnStop() { return; stopped_ = true; + // We should never touch |io_loop_| after being stopped, so post our final + // task to clean up. io_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, &AudioRendererImpl::OnDestroy)); } @@ -117,10 +119,16 @@ void AudioRendererImpl::OnReadComplete(media::Buffer* buffer_in) { void AudioRendererImpl::SetPlaybackRate(float rate) { DCHECK(rate >= 0.0f); + AutoLock auto_lock(lock_); + // Handle the case where we stopped due to |io_loop_| dying. + if (stopped_) { + AudioRendererBase::SetPlaybackRate(rate); + return; + } + // We have two cases here: // Play: GetPlaybackRate() == 0.0 && rate != 0.0 // Pause: GetPlaybackRate() != 0.0 && rate == 0.0 - AutoLock auto_lock(lock_); if (GetPlaybackRate() == 0.0f && rate != 0.0f) { // Play is a bit tricky, we can only play if we have done prerolling. // TODO(hclam): I should check for end of streams status here. @@ -227,6 +235,7 @@ void AudioRendererImpl::OnCreateStream( // Make sure we don't call create more than once. DCHECK_EQ(0, stream_id_); stream_id_ = filter_->AddDelegate(this); + io_loop_->AddDestructionObserver(this); ViewHostMsg_Audio_CreateStream params; params.format = format; @@ -254,8 +263,24 @@ void AudioRendererImpl::OnPause() { void AudioRendererImpl::OnDestroy() { DCHECK(MessageLoop::current() == io_loop_); + // Make sure we don't call destroy more than once. + DCHECK_NE(0, stream_id_); filter_->RemoveDelegate(stream_id_); filter_->Send(new ViewHostMsg_CloseAudioStream(0, stream_id_)); + io_loop_->RemoveDestructionObserver(this); + stream_id_ = 0; +} + +void AudioRendererImpl::WillDestroyCurrentMessageLoop() { + DCHECK(MessageLoop::current() == io_loop_); + + // We treat the IO loop going away the same as stopping. + AutoLock auto_lock(lock_); + if (stopped_) + return; + + stopped_ = true; + OnDestroy(); } void AudioRendererImpl::OnSetVolume(double volume) { diff --git a/chrome/renderer/media/audio_renderer_impl.h b/chrome/renderer/media/audio_renderer_impl.h index aec60e7..01885be 100644 --- a/chrome/renderer/media/audio_renderer_impl.h +++ b/chrome/renderer/media/audio_renderer_impl.h @@ -100,11 +100,13 @@ #include "media/base/factory.h" #include "media/base/filters.h" #include "media/filters/audio_renderer_base.h" +#include "testing/gtest/include/gtest/gtest_prod.h" class AudioMessageFilter; class AudioRendererImpl : public media::AudioRendererBase, - public AudioMessageFilter::Delegate { + public AudioMessageFilter::Delegate, + public MessageLoop::DestructionObserver { public: // Methods called on render thread ------------------------------------------ // Methods called during construction. @@ -142,6 +144,11 @@ class AudioRendererImpl : public media::AudioRendererBase, friend class media::FilterFactoryImpl1<AudioRendererImpl, AudioMessageFilter*>; + // For access to constructor and IO thread methods. + friend class AudioRendererImplTest; + FRIEND_TEST(AudioRendererImplTest, Stop); + FRIEND_TEST(AudioRendererImplTest, DestroyedMessageLoop_OnReadComplete); + explicit AudioRendererImpl(AudioMessageFilter* filter); virtual ~AudioRendererImpl(); @@ -163,6 +170,9 @@ class AudioRendererImpl : public media::AudioRendererBase, void OnNotifyPacketReady(); void OnDestroy(); + // Called on IO thread when message loop is dying. + virtual void WillDestroyCurrentMessageLoop(); + // Information about the audio stream. int channels_; int sample_rate_; diff --git a/chrome/renderer/media/audio_renderer_impl_unittest.cc b/chrome/renderer/media/audio_renderer_impl_unittest.cc new file mode 100644 index 0000000..a3b3420 --- /dev/null +++ b/chrome/renderer/media/audio_renderer_impl_unittest.cc @@ -0,0 +1,129 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/common/render_messages.h" +#include "chrome/renderer/media/audio_renderer_impl.h" +#include "media/base/data_buffer.h" +#include "media/base/mock_filter_host.h" +#include "media/base/mock_filters.h" +#include "testing/gtest/include/gtest/gtest.h" + +class AudioRendererImplTest : public ::testing::Test { + public: + static const int kRouteId = 0; + static const int kSize = 1024; + + AudioRendererImplTest() { + message_loop_.reset(new MessageLoop(MessageLoop::TYPE_IO)); + + // TODO(scherkus): use gmock with AudioMessageFilter to verify + // AudioRendererImpl calls or doesn't call Send(). + filter_ = new AudioMessageFilter(kRouteId); + filter_->message_loop_ = message_loop_.get(); + + // Create temporary shared memory. + CHECK(shared_mem_.Create(L"", false, false, kSize)); + + // Setup expectations for initialization. + EXPECT_CALL(callback_, OnFilterCallback()); + EXPECT_CALL(callback_, OnCallbackDestroyed()); + decoder_ = new media::MockAudioDecoder(2, 48000, 16); + + // Create and initialize audio renderer. + renderer_ = new AudioRendererImpl(filter_); + renderer_->set_host(&host_); + renderer_->set_message_loop(message_loop_.get()); + renderer_->Initialize(decoder_, callback_.NewCallback()); + + // Run pending tasks and simulate responding with a created audio stream. + message_loop_->RunAllPending(); + renderer_->OnCreated(shared_mem_.handle(), kSize); + } + + virtual ~AudioRendererImplTest() { + } + + protected: + // Fixtures. + scoped_ptr<MessageLoop> message_loop_; + scoped_refptr<AudioMessageFilter> filter_; + base::SharedMemory shared_mem_; + media::MockFilterHost host_; + media::MockFilterCallback callback_; + scoped_refptr<media::MockAudioDecoder> decoder_; + scoped_refptr<AudioRendererImpl> renderer_; + + private: + DISALLOW_COPY_AND_ASSIGN(AudioRendererImplTest); +}; + +TEST_F(AudioRendererImplTest, SetPlaybackRate) { + // Execute SetPlaybackRate() codepath to create an IPC message. + + // Toggle play/pause to generate some IPC messages. + renderer_->SetPlaybackRate(0.0f); + renderer_->SetPlaybackRate(1.0f); + renderer_->SetPlaybackRate(0.0f); + + message_loop_->RunAllPending(); + renderer_->Stop(); +} + +TEST_F(AudioRendererImplTest, SetVolume) { + // Execute SetVolume() codepath to create an IPC message. + renderer_->SetVolume(0.5f); + message_loop_->RunAllPending(); + renderer_->Stop(); +} + +TEST_F(AudioRendererImplTest, Stop) { + // Declare some state messages. + const ViewMsg_AudioStreamState kError = + { ViewMsg_AudioStreamState::kError }; + const ViewMsg_AudioStreamState kPlaying = + { ViewMsg_AudioStreamState::kPlaying }; + const ViewMsg_AudioStreamState kPaused = + { ViewMsg_AudioStreamState::kPaused }; + + // Execute Stop() codepath to create an IPC message. + renderer_->Stop(); + message_loop_->RunAllPending(); + + // Run AudioMessageFilter::Delegate methods, which can be executed after being + // stopped. AudioRendererImpl shouldn't create any messages. + renderer_->OnRequestPacket(kSize, base::Time()); + renderer_->OnStateChanged(kError); + renderer_->OnStateChanged(kPlaying); + renderer_->OnStateChanged(kPaused); + renderer_->OnCreated(shared_mem_.handle(), kSize); + renderer_->OnVolume(0.5); + + // It's possible that the upstream decoder replies right after being stopped. + scoped_refptr<media::Buffer> buffer = new media::DataBuffer(kSize); + renderer_->OnReadComplete(buffer); +} + +TEST_F(AudioRendererImplTest, DestroyedMessageLoop_SetPlaybackRate) { + // Kill the message loop and verify SetPlaybackRate() still works. + message_loop_.reset(); + renderer_->SetPlaybackRate(0.0f); + renderer_->SetPlaybackRate(1.0f); + renderer_->SetPlaybackRate(0.0f); + renderer_->Stop(); +} + +TEST_F(AudioRendererImplTest, DestroyedMessageLoop_SetVolume) { + // Kill the message loop and verify SetVolume() still works. + message_loop_.reset(); + renderer_->SetVolume(0.5f); + renderer_->Stop(); +} + +TEST_F(AudioRendererImplTest, DestroyedMessageLoop_OnReadComplete) { + // Kill the message loop and verify OnReadComplete() still works. + message_loop_.reset(); + scoped_refptr<media::Buffer> buffer = new media::DataBuffer(kSize); + renderer_->OnReadComplete(buffer); + renderer_->Stop(); +} diff --git a/media/filters/audio_renderer_base.cc b/media/filters/audio_renderer_base.cc index 65829f6..4ec7403 100644 --- a/media/filters/audio_renderer_base.cc +++ b/media/filters/audio_renderer_base.cc @@ -131,6 +131,13 @@ void AudioRendererBase::OnReadComplete(Buffer* buffer_in) { DCHECK_GT(pending_reads_, 0u); --pending_reads_; + // TODO(scherkus): this happens due to a race, primarily because Stop() is a + // synchronous call when it should be asynchronous and accept a callback. + // Refer to http://crbug.com/16059 + if (state_ == kStopped) { + return; + } + // Don't enqueue an end-of-stream buffer because it has no data. if (buffer_in->IsEndOfStream()) { recieved_end_of_stream_ = true; |