diff options
author | miu@chromium.org <miu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-01-11 09:31:47 +0000 |
---|---|---|
committer | miu@chromium.org <miu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-01-11 09:31:47 +0000 |
commit | f8d1737f16939011ce55ab2a7ce438bc7172b87c (patch) | |
tree | fdfd957972bd74cfbe5c1122bd54a0449bf75ebd | |
parent | 98efbd9c6e1dd6db2a4f73eca2ce79ac77d3d0db (diff) | |
download | chromium_src-f8d1737f16939011ce55ab2a7ce438bc7172b87c.zip chromium_src-f8d1737f16939011ce55ab2a7ce438bc7172b87c.tar.gz chromium_src-f8d1737f16939011ce55ab2a7ce438bc7172b87c.tar.bz2 |
Tab Audio Mirroring/Capture: Browser-side connect/disconnect functionality:
1. Added new AudioMirroringManager to dynamically match/route "divertable" audio streams with mirroring destinations.
2. Modified AudioOutputController to provide "divert audio data" functionality.
3. Modified AudioRendererHost to notify AudioMirroringManager of all audio streams.
The intention is, in a later change, to introduce a "WebContentsAudioInputStream" which will implement the AudioMirroringManager::MirroringDestination interface introduced in this change. WCAIS will represent the lifetime of a tab audio mirroring session, calling AudioMirroringManager::Start/StopMirroring() as appropriate.
Testing:
1. Rewrote most of unit testing for AudioOutputController, addressing bug 112500. Also added testing for the new Divert functionality.
2. Added extensive unit testing for the new Start/StopMirroring functionality in AudioMirroringManager.
3. Minor testing clean-ups/additions elsewhere.
BUG=153392,112500
TEST=Run media_unittests and content_unittests.
Review URL: https://chromiumcodereview.appspot.com/11413078
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@176295 0039d316-1c4b-4281-b951-d872f2087c98
18 files changed, 1039 insertions, 279 deletions
diff --git a/content/browser/browser_main_loop.cc b/content/browser/browser_main_loop.cc index e7faa9e..55f8cf1 100644 --- a/content/browser/browser_main_loop.cc +++ b/content/browser/browser_main_loop.cc @@ -27,6 +27,7 @@ #include "content/browser/loader/resource_dispatcher_host_impl.h" #include "content/browser/net/browser_online_state_observer.h" #include "content/browser/plugin_service_impl.h" +#include "content/browser/renderer_host/media/audio_mirroring_manager.h" #include "content/browser/renderer_host/media/media_stream_manager.h" #include "content/browser/speech/speech_recognition_manager_impl.h" #include "content/browser/trace_controller_impl.h" @@ -227,6 +228,11 @@ media::AudioManager* BrowserMainLoop::GetAudioManager() { } // static +AudioMirroringManager* BrowserMainLoop::GetAudioMirroringManager() { + return g_current_browser_main_loop->audio_mirroring_manager_.get(); +} + +// static MediaStreamManager* BrowserMainLoop::GetMediaStreamManager() { return g_current_browser_main_loop->media_stream_manager_.get(); } @@ -343,6 +349,9 @@ void BrowserMainLoop::MainMessageLoopStart() { hi_res_timer_manager_.reset(new HighResolutionTimerManager); network_change_notifier_.reset(net::NetworkChangeNotifier::Create()); audio_manager_.reset(media::AudioManager::Create()); +#if !defined(OS_IOS) + audio_mirroring_manager_.reset(new AudioMirroringManager()); +#endif #if !defined(OS_IOS) // Start tracing to a file if needed. diff --git a/content/browser/browser_main_loop.h b/content/browser/browser_main_loop.h index 491e5b6..284ecf6 100644 --- a/content/browser/browser_main_loop.h +++ b/content/browser/browser_main_loop.h @@ -26,6 +26,7 @@ class NetworkChangeNotifier; } namespace content { +class AudioMirroringManager; class BrowserMainParts; class BrowserOnlineStateObserver; class BrowserShutdownImpl; @@ -71,6 +72,7 @@ class BrowserMainLoop { // Can be called on any thread. static media::AudioManager* GetAudioManager(); + static AudioMirroringManager* GetAudioMirroringManager(); static MediaStreamManager* GetMediaStreamManager(); private: @@ -95,6 +97,7 @@ class BrowserMainLoop { scoped_ptr<HighResolutionTimerManager> hi_res_timer_manager_; scoped_ptr<net::NetworkChangeNotifier> network_change_notifier_; scoped_ptr<media::AudioManager> audio_manager_; + scoped_ptr<AudioMirroringManager> audio_mirroring_manager_; scoped_ptr<MediaStreamManager> media_stream_manager_; // Per-process listener for online state changes. scoped_ptr<BrowserOnlineStateObserver> online_state_observer_; diff --git a/content/browser/renderer_host/media/audio_mirroring_manager.cc b/content/browser/renderer_host/media/audio_mirroring_manager.cc new file mode 100644 index 0000000..8a2bc4b --- /dev/null +++ b/content/browser/renderer_host/media/audio_mirroring_manager.cc @@ -0,0 +1,164 @@ +// Copyright (c) 2013 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 "content/browser/renderer_host/media/audio_mirroring_manager.h" + +#include "content/public/browser/browser_thread.h" + +namespace content { + +namespace { + +// Debug utility to make sure methods of AudioMirroringManager are not invoked +// more than once in a single call stack. In release builds, this compiles to +// nothing and gets completely optimized out. +class ReentrancyGuard { + public: +#ifdef NDEBUG + ReentrancyGuard() {} + ~ReentrancyGuard() {} +#else + ReentrancyGuard() { + DCHECK(!inside_a_method_); + inside_a_method_ = true; + } + ~ReentrancyGuard() { + inside_a_method_ = false; + } + + static bool inside_a_method_; // Safe to be static, since AMM is a singleton. +#endif +}; + +#ifndef NDEBUG +bool ReentrancyGuard::inside_a_method_ = false; +#endif + +} // namespace + +AudioMirroringManager::AudioMirroringManager() {} + +AudioMirroringManager::~AudioMirroringManager() { + DCHECK(diverters_.empty()); + DCHECK(sessions_.empty()); +} + +void AudioMirroringManager::AddDiverter( + int render_process_id, int render_view_id, Diverter* diverter) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + ReentrancyGuard guard; + DCHECK(diverter); + + // DCHECK(diverter not already in diverters_ under any key) +#ifndef NDEBUG + for (DiverterMap::const_iterator it = diverters_.begin(); + it != diverters_.end(); ++it) { + DCHECK_NE(diverter, it->second); + } +#endif + + // Add the diverter to the set of active diverters. + const Target target(render_process_id, render_view_id); + diverters_.insert(std::make_pair(target, diverter)); + + // If a mirroring session is active, start diverting the audio stream + // immediately. + SessionMap::iterator session_it = sessions_.find(target); + if (session_it != sessions_.end()) { + diverter->StartDiverting( + session_it->second->AddInput(diverter->GetAudioParameters())); + } +} + +void AudioMirroringManager::RemoveDiverter( + int render_process_id, int render_view_id, Diverter* diverter) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + ReentrancyGuard guard; + + // Stop diverting the audio stream if a mirroring session is active. + const Target target(render_process_id, render_view_id); + SessionMap::iterator session_it = sessions_.find(target); + if (session_it != sessions_.end()) + diverter->StopDiverting(); + + // Remove the diverter from the set of active diverters. + for (DiverterMap::iterator it = diverters_.lower_bound(target); + it != diverters_.end() && it->first == target; ++it) { + if (it->second == diverter) { + diverters_.erase(it); + break; + } + } +} + +void AudioMirroringManager::StartMirroring( + int render_process_id, int render_view_id, + MirroringDestination* destination) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + ReentrancyGuard guard; + DCHECK(destination); + + // Insert an entry into the set of active mirroring sessions. If a mirroring + // session is already active for |render_process_id| + |render_view_id|, + // replace the entry. + const Target target(render_process_id, render_view_id); + SessionMap::iterator session_it = sessions_.find(target); + MirroringDestination* old_destination; + if (session_it == sessions_.end()) { + old_destination = NULL; + sessions_.insert(std::make_pair(target, destination)); + + DVLOG(1) << "Start mirroring render_process_id:render_view_id=" + << render_process_id << ':' << render_view_id + << " --> MirroringDestination@" << destination; + } else { + old_destination = session_it->second; + session_it->second = destination; + + DVLOG(1) << "Switch mirroring of render_process_id:render_view_id=" + << render_process_id << ':' << render_view_id + << " MirroringDestination@" << old_destination + << " --> MirroringDestination@" << destination; + } + + // Divert audio streams coming from |target| to |destination|. If streams + // were already diverted to the |old_destination|, remove them. + for (DiverterMap::iterator it = diverters_.lower_bound(target); + it != diverters_.end() && it->first == target; ++it) { + Diverter* const diverter = it->second; + if (old_destination) + diverter->StopDiverting(); + diverter->StartDiverting( + destination->AddInput(diverter->GetAudioParameters())); + } +} + +void AudioMirroringManager::StopMirroring( + int render_process_id, int render_view_id, + MirroringDestination* destination) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + ReentrancyGuard guard; + + // Stop mirroring if there is an active session *and* the destination + // matches. + const Target target(render_process_id, render_view_id); + SessionMap::iterator session_it = sessions_.find(target); + if (session_it == sessions_.end() || destination != session_it->second) + return; + + DVLOG(1) << "Stop mirroring render_process_id:render_view_id=" + << render_process_id << ':' << render_view_id + << " --> MirroringDestination@" << destination; + + // Stop diverting each audio stream in the mirroring session being stopped. + for (DiverterMap::iterator it = diverters_.lower_bound(target); + it != diverters_.end() && it->first == target; ++it) { + it->second->StopDiverting(); + } + + // Remove the entry from the set of active mirroring sessions. + sessions_.erase(session_it); +} + +} // namespace content diff --git a/content/browser/renderer_host/media/audio_mirroring_manager.h b/content/browser/renderer_host/media/audio_mirroring_manager.h new file mode 100644 index 0000000..0db4f17 --- /dev/null +++ b/content/browser/renderer_host/media/audio_mirroring_manager.h @@ -0,0 +1,108 @@ +// Copyright (c) 2013 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. +// +// AudioMirroringManager is a singleton object that maintains a set of active +// audio mirroring destinations and auto-connects/disconnects audio streams +// to/from those destinations. It is meant to be used exclusively on the IO +// BrowserThread. +// +// How it works: +// +// 1. AudioRendererHost gets a CreateStream message from the render process +// and, among other things, creates an AudioOutputController to control the +// audio data flow between the render and browser processes. +// 2. At some point, AudioRendererHost receives an "associate with render +// view" message. Among other actions, it registers the +// AudioOutputController with AudioMirroringManager (as a Diverter). +// 3. A user request to mirror all the audio for a single RenderView is made. +// A MirroringDestination is created, and StartMirroring() is called to +// begin the mirroring session. This causes AudioMirroringManager to +// instruct any matching Diverters to divert their audio data to the +// MirroringDestination. +// +// #2 and #3 above may occur in any order, as it is the job of +// AudioMirroringManager to realize when the players can be "matched up." + +#ifndef CONTENT_BROWSER_RENDERER_HOST_MEDIA_AUDIO_MIRRORING_MANAGER_H_ +#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_AUDIO_MIRRORING_MANAGER_H_ + +#include <map> +#include <utility> + +#include "base/basictypes.h" +#include "content/common/content_export.h" +#include "media/audio/audio_source_diverter.h" + +namespace media { +class AudioOutputStream; +} + +namespace content { + +class CONTENT_EXPORT AudioMirroringManager { + public: + // Interface for diverting audio data to an alternative AudioOutputStream. + typedef media::AudioSourceDiverter Diverter; + + // Interface to be implemented by audio mirroring destinations. See comments + // for StartMirroring() and StopMirroring() below. + class MirroringDestination { + public: + // Create a consumer of audio data in the format specified by |params|, and + // connect it as an input to mirroring. When Close() is called on the + // returned AudioOutputStream, the input is disconnected and the object + // becomes invalid. + virtual media::AudioOutputStream* AddInput( + const media::AudioParameters& params) = 0; + + protected: + virtual ~MirroringDestination() {} + }; + + AudioMirroringManager(); + + virtual ~AudioMirroringManager(); + + // Add/Remove a diverter for an audio stream with a known RenderView target + // (represented by |render_process_id| + |render_view_id|). Multiple + // diverters may be added for the same target. |diverter| must live until + // after RemoveDiverter() is called. + // + // Re-entrancy warning: These methods should not be called by a Diverter + // during a Start/StopDiverting() invocation. + virtual void AddDiverter(int render_process_id, int render_view_id, + Diverter* diverter); + virtual void RemoveDiverter(int render_process_id, int render_view_id, + Diverter* diverter); + + // Start/stop mirroring all audio output streams associated with a RenderView + // target (represented by |render_process_id| + |render_view_id|) to + // |destination|. |destination| must live until after StopMirroring() is + // called. + virtual void StartMirroring(int render_process_id, int render_view_id, + MirroringDestination* destination); + virtual void StopMirroring(int render_process_id, int render_view_id, + MirroringDestination* destination); + + private: + // A mirroring target is a RenderView identified by a + // <render_process_id, render_view_id> pair. + typedef std::pair<int, int> Target; + + // Note: Objects in these maps are not owned. + typedef std::multimap<Target, Diverter*> DiverterMap; + typedef std::map<Target, MirroringDestination*> SessionMap; + + // Currently-active divertable audio streams. + DiverterMap diverters_; + + // Currently-active mirroring sessions. + SessionMap sessions_; + + DISALLOW_COPY_AND_ASSIGN(AudioMirroringManager); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_AUDIO_MIRRORING_MANAGER_H_ diff --git a/content/browser/renderer_host/media/audio_mirroring_manager_unittest.cc b/content/browser/renderer_host/media/audio_mirroring_manager_unittest.cc new file mode 100644 index 0000000..3cf38ed --- /dev/null +++ b/content/browser/renderer_host/media/audio_mirroring_manager_unittest.cc @@ -0,0 +1,234 @@ +// Copyright (c) 2013 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 "content/browser/renderer_host/media/audio_mirroring_manager.h" + +#include <map> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/message_loop.h" +#include "base/synchronization/waitable_event.h" +#include "content/browser/browser_thread_impl.h" +#include "media/audio/audio_parameters.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using media::AudioOutputStream; +using media::AudioParameters; +using testing::_; +using testing::NotNull; +using testing::Ref; +using testing::Return; +using testing::ReturnRef; + +namespace content { + +namespace { + +class MockDiverter : public AudioMirroringManager::Diverter { + public: + MOCK_METHOD0(GetAudioParameters, const AudioParameters&()); + MOCK_METHOD1(StartDiverting, void(AudioOutputStream*)); + MOCK_METHOD0(StopDiverting, void()); +}; + +class MockMirroringDestination + : public AudioMirroringManager::MirroringDestination { + public: + MOCK_METHOD1(AddInput, + media::AudioOutputStream*(const media::AudioParameters& params)); +}; + +} // namespace + +class AudioMirroringManagerTest : public testing::Test { + public: + AudioMirroringManagerTest() + : message_loop_(MessageLoop::TYPE_IO), + io_thread_(BrowserThread::IO, &message_loop_), + params_(AudioParameters::AUDIO_FAKE, media::CHANNEL_LAYOUT_STEREO, + AudioParameters::kAudioCDSampleRate, 16, + AudioParameters::kAudioCDSampleRate / 10) {} + + MockDiverter* CreateStream( + int render_process_id, int render_view_id, int expected_times_diverted) { + MockDiverter* const diverter = new MockDiverter(); + if (expected_times_diverted > 0) { + EXPECT_CALL(*diverter, GetAudioParameters()) + .Times(expected_times_diverted) + .WillRepeatedly(ReturnRef(params_)); + EXPECT_CALL(*diverter, StartDiverting(NotNull())) + .Times(expected_times_diverted); + EXPECT_CALL(*diverter, StopDiverting()) + .Times(expected_times_diverted); + } + + mirroring_manager_.AddDiverter(render_process_id, render_view_id, diverter); + + return diverter; + } + + void KillStream( + int render_process_id, int render_view_id, MockDiverter* diverter) { + mirroring_manager_.RemoveDiverter( + render_process_id, render_view_id, diverter); + + delete diverter; + } + + MockMirroringDestination* StartMirroringTo( + int render_process_id, int render_view_id, int expected_inputs_added) { + MockMirroringDestination* const dest = new MockMirroringDestination(); + if (expected_inputs_added > 0) { + static AudioOutputStream* const kNonNullPointer = + reinterpret_cast<AudioOutputStream*>(0x11111110); + EXPECT_CALL(*dest, AddInput(Ref(params_))) + .Times(expected_inputs_added) + .WillRepeatedly(Return(kNonNullPointer)); + } + + mirroring_manager_.StartMirroring(render_process_id, render_view_id, dest); + + return dest; + } + + void StopMirroringTo(int render_process_id, int render_view_id, + MockMirroringDestination* dest) { + mirroring_manager_.StopMirroring(render_process_id, render_view_id, dest); + + delete dest; +} + + private: + MessageLoop message_loop_; + BrowserThreadImpl io_thread_; + AudioParameters params_; + AudioMirroringManager mirroring_manager_; + + DISALLOW_COPY_AND_ASSIGN(AudioMirroringManagerTest); +}; + +namespace { +const int kRenderProcessId = 123; +const int kRenderViewId = 456; +const int kAnotherRenderProcessId = 789; +const int kAnotherRenderViewId = 1234; +const int kYetAnotherRenderProcessId = 4560; +const int kYetAnotherRenderViewId = 7890; +} + +TEST_F(AudioMirroringManagerTest, MirroringSessionOfNothing) { + MockMirroringDestination* const destination = + StartMirroringTo(kRenderProcessId, kRenderViewId, 0); + StopMirroringTo(kRenderProcessId, kRenderViewId, destination); +} + +TEST_F(AudioMirroringManagerTest, TwoMirroringSessionsOfNothing) { + MockMirroringDestination* const destination = + StartMirroringTo(kRenderProcessId, kRenderViewId, 0); + StopMirroringTo(kRenderProcessId, kRenderViewId, destination); + + MockMirroringDestination* const another_destination = + StartMirroringTo(kAnotherRenderProcessId, kAnotherRenderViewId, 0); + StopMirroringTo(kAnotherRenderProcessId, kAnotherRenderViewId, + another_destination); +} + +TEST_F(AudioMirroringManagerTest, SwitchMirroringDestinationNoStreams) { + MockMirroringDestination* const destination = + StartMirroringTo(kRenderProcessId, kRenderViewId, 0); + MockMirroringDestination* const new_destination = + StartMirroringTo(kRenderProcessId, kRenderViewId, 0); + StopMirroringTo(kRenderProcessId, kRenderViewId, destination); + StopMirroringTo(kRenderProcessId, kRenderViewId, new_destination); +} + +TEST_F(AudioMirroringManagerTest, StreamLifetimeAroundMirroringSession) { + MockDiverter* const stream = CreateStream(kRenderProcessId, kRenderViewId, 1); + MockMirroringDestination* const destination = + StartMirroringTo(kRenderProcessId, kRenderViewId, 1); + StopMirroringTo(kRenderProcessId, kRenderViewId, destination); + KillStream(kRenderProcessId, kRenderViewId, stream); +} + +TEST_F(AudioMirroringManagerTest, StreamLifetimeWithinMirroringSession) { + MockMirroringDestination* const destination = + StartMirroringTo(kRenderProcessId, kRenderViewId, 1); + MockDiverter* const stream = CreateStream(kRenderProcessId, kRenderViewId, 1); + KillStream(kRenderProcessId, kRenderViewId, stream); + StopMirroringTo(kRenderProcessId, kRenderViewId, destination); +} + +TEST_F(AudioMirroringManagerTest, StreamLifetimeAroundTwoMirroringSessions) { + MockDiverter* const stream = CreateStream(kRenderProcessId, kRenderViewId, 2); + MockMirroringDestination* const destination = + StartMirroringTo(kRenderProcessId, kRenderViewId, 1); + StopMirroringTo(kRenderProcessId, kRenderViewId, destination); + MockMirroringDestination* const new_destination = + StartMirroringTo(kRenderProcessId, kRenderViewId, 1); + StopMirroringTo(kRenderProcessId, kRenderViewId, new_destination); + KillStream(kRenderProcessId, kRenderViewId, stream); +} + +TEST_F(AudioMirroringManagerTest, StreamLifetimeWithinTwoMirroringSessions) { + MockMirroringDestination* const destination = + StartMirroringTo(kRenderProcessId, kRenderViewId, 1); + MockDiverter* const stream = CreateStream(kRenderProcessId, kRenderViewId, 2); + StopMirroringTo(kRenderProcessId, kRenderViewId, destination); + MockMirroringDestination* const new_destination = + StartMirroringTo(kRenderProcessId, kRenderViewId, 1); + KillStream(kRenderProcessId, kRenderViewId, stream); + StopMirroringTo(kRenderProcessId, kRenderViewId, new_destination); +} + +TEST_F(AudioMirroringManagerTest, MultipleStreamsInOneMirroringSession) { + MockDiverter* const stream1 = + CreateStream(kRenderProcessId, kRenderViewId, 1); + MockMirroringDestination* const destination = + StartMirroringTo(kRenderProcessId, kRenderViewId, 3); + MockDiverter* const stream2 = + CreateStream(kRenderProcessId, kRenderViewId, 1); + MockDiverter* const stream3 = + CreateStream(kRenderProcessId, kRenderViewId, 1); + KillStream(kRenderProcessId, kRenderViewId, stream2); + StopMirroringTo(kRenderProcessId, kRenderViewId, destination); + KillStream(kRenderProcessId, kRenderViewId, stream3); + KillStream(kRenderProcessId, kRenderViewId, stream1); +} + +// A random interleaving of operations for three separate targets, each of which +// has one stream mirrored to one destination. +TEST_F(AudioMirroringManagerTest, ThreeSeparateMirroringSessions) { + MockDiverter* const stream = + CreateStream(kRenderProcessId, kRenderViewId, 1); + MockMirroringDestination* const destination = + StartMirroringTo(kRenderProcessId, kRenderViewId, 1); + + MockMirroringDestination* const another_destination = + StartMirroringTo(kAnotherRenderProcessId, kAnotherRenderViewId, 1); + MockDiverter* const another_stream = + CreateStream(kAnotherRenderProcessId, kAnotherRenderViewId, 1); + + KillStream(kRenderProcessId, kRenderViewId, stream); + + MockDiverter* const yet_another_stream = + CreateStream(kYetAnotherRenderProcessId, kYetAnotherRenderViewId, 1); + MockMirroringDestination* const yet_another_destination = + StartMirroringTo(kYetAnotherRenderProcessId, kYetAnotherRenderViewId, 1); + + StopMirroringTo(kAnotherRenderProcessId, kAnotherRenderViewId, + another_destination); + + StopMirroringTo(kYetAnotherRenderProcessId, kYetAnotherRenderViewId, + yet_another_destination); + + StopMirroringTo(kRenderProcessId, kRenderViewId, destination); + + KillStream(kAnotherRenderProcessId, kAnotherRenderViewId, another_stream); + KillStream(kYetAnotherRenderProcessId, kYetAnotherRenderViewId, + yet_another_stream); +} + +} // namespace content diff --git a/content/browser/renderer_host/media/audio_renderer_host.cc b/content/browser/renderer_host/media/audio_renderer_host.cc index ac9b2c6..3fb7bc9 100644 --- a/content/browser/renderer_host/media/audio_renderer_host.cc +++ b/content/browser/renderer_host/media/audio_renderer_host.cc @@ -9,6 +9,7 @@ #include "base/process.h" #include "base/shared_memory.h" #include "content/browser/browser_main_loop.h" +#include "content/browser/renderer_host/media/audio_mirroring_manager.h" #include "content/browser/renderer_host/media/audio_sync_reader.h" #include "content/common/media/audio_messages.h" #include "content/public/browser/media_observer.h" @@ -30,6 +31,9 @@ struct AudioRendererHost::AudioEntry { // The audio stream ID. int stream_id; + // The routing ID of the source render view. + int render_view_id; + // Shared memory for transmission of the audio data. base::SharedMemory shared_memory; @@ -43,6 +47,7 @@ struct AudioRendererHost::AudioEntry { AudioRendererHost::AudioEntry::AudioEntry() : stream_id(0), + render_view_id(MSG_ROUTING_NONE), pending_close(false) { } @@ -51,9 +56,15 @@ AudioRendererHost::AudioEntry::~AudioEntry() {} /////////////////////////////////////////////////////////////////////////////// // AudioRendererHost implementations. AudioRendererHost::AudioRendererHost( - media::AudioManager* audio_manager, MediaObserver* media_observer) - : audio_manager_(audio_manager), + int render_process_id, + media::AudioManager* audio_manager, + AudioMirroringManager* mirroring_manager, + MediaObserver* media_observer) + : render_process_id_(render_process_id), + audio_manager_(audio_manager), + mirroring_manager_(mirroring_manager), media_observer_(media_observer) { + DCHECK(audio_manager_); } AudioRendererHost::~AudioRendererHost() { @@ -281,10 +292,30 @@ void AudioRendererHost::OnCreateStream( void AudioRendererHost::OnAssociateStreamWithProducer(int stream_id, int render_view_id) { - // TODO(miu): Will use render_view_id in upcoming change. + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + DVLOG(1) << "AudioRendererHost@" << this << "::OnAssociateStreamWithProducer(stream_id=" << stream_id << ", render_view_id=" << render_view_id << ")"; + + AudioEntry* const entry = LookupById(stream_id); + if (!entry) { + SendErrorMessage(stream_id); + return; + } + + if (entry->render_view_id == render_view_id) + return; + + if (mirroring_manager_) { + mirroring_manager_->RemoveDiverter( + render_process_id_, entry->render_view_id, entry->controller); + } + entry->render_view_id = render_view_id; + if (mirroring_manager_) { + mirroring_manager_->AddDiverter( + render_process_id_, entry->render_view_id, entry->controller); + } } void AudioRendererHost::OnPlayStream(int stream_id) { @@ -376,6 +407,10 @@ void AudioRendererHost::CloseAndDeleteStream(AudioEntry* entry) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); if (!entry->pending_close) { + if (mirroring_manager_) { + mirroring_manager_->RemoveDiverter( + render_process_id_, entry->render_view_id, entry->controller); + } entry->controller->Close( base::Bind(&AudioRendererHost::DeleteEntry, this, entry)); entry->pending_close = true; diff --git a/content/browser/renderer_host/media/audio_renderer_host.h b/content/browser/renderer_host/media/audio_renderer_host.h index 44daef2..3056ade 100644 --- a/content/browser/renderer_host/media/audio_renderer_host.h +++ b/content/browser/renderer_host/media/audio_renderer_host.h @@ -59,6 +59,7 @@ class AudioParameters; namespace content { +class AudioMirroringManager; class MediaObserver; class ResourceContext; @@ -67,7 +68,9 @@ class CONTENT_EXPORT AudioRendererHost public media::AudioOutputController::EventHandler { public: // Called from UI thread from the owner of this object. - AudioRendererHost(media::AudioManager* audio_manager, + AudioRendererHost(int render_process_id, + media::AudioManager* audio_manager, + AudioMirroringManager* mirroring_manager, MediaObserver* media_observer); // BrowserMessageFilter implementation. @@ -164,12 +167,16 @@ class CONTENT_EXPORT AudioRendererHost media::AudioOutputController* LookupControllerByIdForTesting(int stream_id); + // ID of the RenderProcessHost that owns this instance. + const int render_process_id_; + + media::AudioManager* const audio_manager_; + AudioMirroringManager* const mirroring_manager_; + MediaObserver* const media_observer_; + // A map of stream IDs to audio sources. AudioEntryMap audio_entries_; - media::AudioManager* audio_manager_; - MediaObserver* media_observer_; - DISALLOW_COPY_AND_ASSIGN(AudioRendererHost); }; diff --git a/content/browser/renderer_host/media/audio_renderer_host_unittest.cc b/content/browser/renderer_host/media/audio_renderer_host_unittest.cc index e8bd79c..d94fb13 100644 --- a/content/browser/renderer_host/media/audio_renderer_host_unittest.cc +++ b/content/browser/renderer_host/media/audio_renderer_host_unittest.cc @@ -9,6 +9,7 @@ #include "base/process_util.h" #include "base/sync_socket.h" #include "content/browser/browser_thread_impl.h" +#include "content/browser/renderer_host/media/audio_mirroring_manager.h" #include "content/browser/renderer_host/media/audio_renderer_host.h" #include "content/browser/renderer_host/media/mock_media_observer.h" #include "content/common/media/audio_messages.h" @@ -23,28 +24,41 @@ using ::testing::_; using ::testing::AtLeast; using ::testing::DoAll; using ::testing::InSequence; -using ::testing::InvokeWithoutArgs; +using ::testing::NotNull; using ::testing::Return; using ::testing::SaveArg; using ::testing::SetArgumentPointee; namespace content { +static const int kRenderProcessId = 1; +static const int kRenderViewId = 4; static const int kStreamId = 50; -static bool IsRunningHeadless() { - scoped_ptr<base::Environment> env(base::Environment::Create()); - if (env->HasVar("CHROME_HEADLESS")) - return true; - return false; -} +class MockAudioMirroringManager : public AudioMirroringManager { + public: + MockAudioMirroringManager() {} + virtual ~MockAudioMirroringManager() {} + + MOCK_METHOD3(AddDiverter, + void(int render_process_id, int render_view_id, + Diverter* diverter)); + MOCK_METHOD3(RemoveDiverter, + void(int render_process_id, int render_view_id, + Diverter* diverter)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockAudioMirroringManager); +}; class MockAudioRendererHost : public AudioRendererHost { public: explicit MockAudioRendererHost( media::AudioManager* audio_manager, + AudioMirroringManager* mirroring_manager, MediaObserver* media_observer) - : AudioRendererHost(audio_manager, media_observer), + : AudioRendererHost( + kRenderProcessId, audio_manager, mirroring_manager, media_observer), shared_memory_length_(0) { } @@ -142,9 +156,7 @@ ACTION_P(QuitMessageLoop, message_loop) { class AudioRendererHostTest : public testing::Test { public: - AudioRendererHostTest() - : mock_stream_(true) { - } + AudioRendererHostTest() {} protected: virtual void SetUp() { @@ -158,10 +170,8 @@ class AudioRendererHostTest : public testing::Test { message_loop_.get())); audio_manager_.reset(media::AudioManager::Create()); observer_.reset(new MockMediaObserver()); - host_ = new MockAudioRendererHost(audio_manager_.get(), observer_.get()); - - // Expect the audio stream will be deleted. - EXPECT_CALL(*observer_, OnDeleteAudioStream(_, kStreamId)); + host_ = new MockAudioRendererHost( + audio_manager_.get(), &mirroring_manager_, observer_.get()); // Simulate IPC channel connected. host_->OnChannelConnected(base::GetCurrentProcId()); @@ -193,21 +203,34 @@ class AudioRendererHostTest : public testing::Test { EXPECT_CALL(*host_, OnStreamCreated(kStreamId, _)) .WillOnce(QuitMessageLoop(message_loop_.get())); - media::AudioParameters::Format format; - if (mock_stream_) - format = media::AudioParameters::AUDIO_FAKE; - else - format = media::AudioParameters::AUDIO_PCM_LINEAR; - - media::AudioParameters params( - format, media::CHANNEL_LAYOUT_STEREO, - media::AudioParameters::kAudioCDSampleRate, 16, - media::AudioParameters::kAudioCDSampleRate / 10); - // Send a create stream message to the audio output stream and wait until // we receive the created message. - host_->OnCreateStream(kStreamId, params, 0); + host_->OnCreateStream(kStreamId, + media::AudioParameters( + media::AudioParameters::AUDIO_FAKE, + media::CHANNEL_LAYOUT_STEREO, + media::AudioParameters::kAudioCDSampleRate, 16, + media::AudioParameters::kAudioCDSampleRate / 10), + 0); message_loop_->Run(); + + // Simulate the renderer process associating a stream with a render view. + EXPECT_CALL(mirroring_manager_, + RemoveDiverter(kRenderProcessId, MSG_ROUTING_NONE, _)) + .RetiresOnSaturation(); + EXPECT_CALL(mirroring_manager_, + AddDiverter(kRenderProcessId, kRenderViewId, NotNull())) + .RetiresOnSaturation(); + host_->OnAssociateStreamWithProducer(kStreamId, kRenderViewId); + message_loop_->RunUntilIdle(); + // At some point in the future, a corresponding RemoveDiverter() call must + // be made. + EXPECT_CALL(mirroring_manager_, + RemoveDiverter(kRenderProcessId, kRenderViewId, NotNull())) + .RetiresOnSaturation(); + + // Expect the audio stream will be deleted at some later point. + EXPECT_CALL(*observer_, OnDeleteAudioStream(_, kStreamId)); } void Close() { @@ -295,11 +318,9 @@ class AudioRendererHostTest : public testing::Test { message_loop_->Run(); } - void EnableRealDevice() { mock_stream_ = false; } - private: - bool mock_stream_; scoped_ptr<MockMediaObserver> observer_; + MockAudioMirroringManager mirroring_manager_; scoped_refptr<MockAudioRendererHost> host_; scoped_ptr<MessageLoop> message_loop_; scoped_ptr<BrowserThreadImpl> io_thread_; @@ -310,34 +331,22 @@ class AudioRendererHostTest : public testing::Test { }; TEST_F(AudioRendererHostTest, CreateAndClose) { - if (!IsRunningHeadless()) - EnableRealDevice(); - Create(); Close(); } // Simulate the case where a stream is not properly closed. TEST_F(AudioRendererHostTest, CreateAndShutdown) { - if (!IsRunningHeadless()) - EnableRealDevice(); - Create(); } TEST_F(AudioRendererHostTest, CreatePlayAndClose) { - if (!IsRunningHeadless()) - EnableRealDevice(); - Create(); Play(); Close(); } TEST_F(AudioRendererHostTest, CreatePlayPauseAndClose) { - if (!IsRunningHeadless()) - EnableRealDevice(); - Create(); Play(); Pause(); @@ -345,9 +354,6 @@ TEST_F(AudioRendererHostTest, CreatePlayPauseAndClose) { } TEST_F(AudioRendererHostTest, SetVolume) { - if (!IsRunningHeadless()) - EnableRealDevice(); - Create(); SetVolume(0.5); Play(); @@ -357,27 +363,18 @@ TEST_F(AudioRendererHostTest, SetVolume) { // Simulate the case where a stream is not properly closed. TEST_F(AudioRendererHostTest, CreatePlayAndShutdown) { - if (!IsRunningHeadless()) - EnableRealDevice(); - Create(); Play(); } // Simulate the case where a stream is not properly closed. TEST_F(AudioRendererHostTest, CreatePlayPauseAndShutdown) { - if (!IsRunningHeadless()) - EnableRealDevice(); - Create(); Play(); Pause(); } TEST_F(AudioRendererHostTest, SimulateError) { - if (!IsRunningHeadless()) - EnableRealDevice(); - Create(); Play(); SimulateError(); @@ -387,9 +384,6 @@ TEST_F(AudioRendererHostTest, SimulateError) { // the audio device is closed but the render process try to close the // audio stream again. TEST_F(AudioRendererHostTest, SimulateErrorAndClose) { - if (!IsRunningHeadless()) - EnableRealDevice(); - Create(); Play(); SimulateError(); diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc index 00a33c1..6e83b18 100644 --- a/content/browser/renderer_host/render_process_host_impl.cc +++ b/content/browser/renderer_host/render_process_host_impl.cc @@ -66,6 +66,7 @@ #include "content/browser/renderer_host/gamepad_browser_message_filter.h" #include "content/browser/renderer_host/gpu_message_filter.h" #include "content/browser/renderer_host/media/audio_input_renderer_host.h" +#include "content/browser/renderer_host/media/audio_mirroring_manager.h" #include "content/browser/renderer_host/media/audio_renderer_host.h" #include "content/browser/renderer_host/media/media_stream_dispatcher_host.h" #include "content/browser/renderer_host/media/peer_connection_tracker_host.h" @@ -521,7 +522,9 @@ void RenderProcessHostImpl::CreateMessageFilters() { BrowserMainLoop::GetMediaStreamManager(); channel_->AddFilter(new AudioInputRendererHost(audio_manager, media_stream_manager)); - channel_->AddFilter(new AudioRendererHost(audio_manager, media_observer)); + channel_->AddFilter(new AudioRendererHost( + GetID(), audio_manager, BrowserMainLoop::GetAudioMirroringManager(), + media_observer)); channel_->AddFilter(new VideoCaptureHost()); channel_->AddFilter(new AppCacheDispatcherHost( storage_partition_impl_->GetAppCacheService(), diff --git a/content/content_browser.gypi b/content/content_browser.gypi index 99cf831..8c6d2e3 100644 --- a/content/content_browser.gypi +++ b/content/content_browser.gypi @@ -635,6 +635,8 @@ 'browser/renderer_host/media/audio_input_renderer_host.h', 'browser/renderer_host/media/audio_input_sync_writer.cc', 'browser/renderer_host/media/audio_input_sync_writer.h', + 'browser/renderer_host/media/audio_mirroring_manager.cc', + 'browser/renderer_host/media/audio_mirroring_manager.h', 'browser/renderer_host/media/audio_renderer_host.cc', 'browser/renderer_host/media/audio_renderer_host.h', 'browser/renderer_host/media/audio_sync_reader.cc', diff --git a/content/content_tests.gypi b/content/content_tests.gypi index d37a306..becb343 100644 --- a/content/content_tests.gypi +++ b/content/content_tests.gypi @@ -299,6 +299,7 @@ 'browser/plugin_loader_posix_unittest.cc', 'browser/renderer_host/gtk_key_bindings_handler_unittest.cc', 'browser/renderer_host/media/audio_input_device_manager_unittest.cc', + 'browser/renderer_host/media/audio_mirroring_manager_unittest.cc', 'browser/renderer_host/media/audio_renderer_host_unittest.cc', 'browser/renderer_host/media/media_stream_dispatcher_host_unittest.cc', 'browser/renderer_host/media/media_stream_manager_unittest.cc', diff --git a/content/test/webrtc_audio_device_test.cc b/content/test/webrtc_audio_device_test.cc index 72d94e5..b31d4bb 100644 --- a/content/test/webrtc_audio_device_test.cc +++ b/content/test/webrtc_audio_device_test.cc @@ -12,6 +12,7 @@ #include "base/synchronization/waitable_event.h" #include "base/test/test_timeouts.h" #include "content/browser/renderer_host/media/audio_input_renderer_host.h" +#include "content/browser/renderer_host/media/audio_mirroring_manager.h" #include "content/browser/renderer_host/media/audio_renderer_host.h" #include "content/browser/renderer_host/media/media_stream_manager.h" #include "content/browser/renderer_host/media/mock_media_observer.h" @@ -172,6 +173,7 @@ void WebRTCAudioDeviceTest::TearDown() { WaitForIOThreadCompletion(); mock_process_.reset(); media_stream_manager_.reset(); + mirroring_manager_.reset(); audio_manager_.reset(); RendererWebKitPlatformSupportImpl::SetSandboxEnabledForTesting( sandbox_was_enabled_); @@ -206,8 +208,9 @@ void WebRTCAudioDeviceTest::InitializeIOThread(const char* thread_name) { resource_context->set_request_context(test_request_context_.get()); media_observer_.reset(new MockMediaObserver()); - // Create our own AudioManager and MediaStreamManager. + // Create our own AudioManager, AudioMirroringManager and MediaStreamManager. audio_manager_.reset(media::AudioManager::Create()); + mirroring_manager_.reset(new AudioMirroringManager()); media_stream_manager_.reset(new MediaStreamManager(audio_manager_.get())); has_input_devices_ = audio_manager_->HasAudioInputDevices(); @@ -229,8 +232,11 @@ void WebRTCAudioDeviceTest::UninitializeIOThread() { void WebRTCAudioDeviceTest::CreateChannel(const char* name) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + static const int kRenderProcessId = 1; audio_render_host_ = new AudioRendererHost( - audio_manager_.get(), media_observer_.get()); + kRenderProcessId, audio_manager_.get(), mirroring_manager_.get(), + media_observer_.get()); audio_render_host_->OnChannelConnected(base::GetCurrentProcId()); audio_input_renderer_host_ = new AudioInputRendererHost( diff --git a/content/test/webrtc_audio_device_test.h b/content/test/webrtc_audio_device_test.h index 56137192..6409794 100644 --- a/content/test/webrtc_audio_device_test.h +++ b/content/test/webrtc_audio_device_test.h @@ -45,6 +45,7 @@ class ScopedCOMInitializer; namespace content { class AudioInputRendererHost; +class AudioMirroringManager; class AudioRendererHost; class ContentRendererClient; class MediaStreamManager; @@ -169,6 +170,7 @@ class WebRTCAudioDeviceTest : public ::testing::Test, public IPC::Listener { scoped_ptr<MockMediaObserver> media_observer_; scoped_ptr<MediaStreamManager> media_stream_manager_; scoped_ptr<media::AudioManager> audio_manager_; + scoped_ptr<AudioMirroringManager> mirroring_manager_; scoped_ptr<net::URLRequestContext> test_request_context_; scoped_ptr<ResourceContext> resource_context_; scoped_ptr<IPC::Channel> channel_; diff --git a/media/audio/audio_output_controller.cc b/media/audio/audio_output_controller.cc index a9dd2c8..ffeb639 100644 --- a/media/audio/audio_output_controller.cc +++ b/media/audio/audio_output_controller.cc @@ -29,14 +29,15 @@ AudioOutputController::AudioOutputController(AudioManager* audio_manager, const AudioParameters& params, SyncReader* sync_reader) : audio_manager_(audio_manager), + params_(params), handler_(handler), stream_(NULL), + diverting_to_stream_(NULL), volume_(1.0), state_(kEmpty), sync_reader_(sync_reader), message_loop_(audio_manager->GetMessageLoop()), number_polling_attempts_left_(0), - params_(params), ALLOW_THIS_IN_INITIALIZER_LIST(weak_this_(this)) { } @@ -70,13 +71,10 @@ scoped_refptr<AudioOutputController> AudioOutputController::Create( if (!params.IsValid() || !audio_manager) return NULL; - // Starts the audio controller thread. scoped_refptr<AudioOutputController> controller(new AudioOutputController( audio_manager, event_handler, params, sync_reader)); - controller->message_loop_->PostTask(FROM_HERE, base::Bind( &AudioOutputController::DoCreate, controller)); - return controller; } @@ -121,7 +119,8 @@ void AudioOutputController::DoCreate() { DoStopCloseAndClearStream(NULL); // Calls RemoveOutputDeviceChangeListener(). - stream_ = audio_manager_->MakeAudioOutputStreamProxy(params_); + stream_ = diverting_to_stream_ ? diverting_to_stream_ : + audio_manager_->MakeAudioOutputStreamProxy(params_); if (!stream_) { state_ = kError; @@ -139,9 +138,10 @@ void AudioOutputController::DoCreate() { return; } - // Everything started okay, so register for state change callbacks if we have - // not already done so. - audio_manager_->AddOutputDeviceChangeListener(this); + // Everything started okay, so re-register for state change callbacks if + // stream_ was created via AudioManager. + if (stream_ != diverting_to_stream_) + audio_manager_->AddOutputDeviceChangeListener(this); // We have successfully opened the stream. Set the initial volume. stream_->SetVolume(volume_); @@ -349,10 +349,15 @@ void AudioOutputController::DoStopCloseAndClearStream(WaitableEvent* done) { // Allow calling unconditionally and bail if we don't have a stream_ to close. if (stream_) { - audio_manager_->RemoveOutputDeviceChangeListener(this); + // De-register from state change callbacks if stream_ was created via + // AudioManager. + if (stream_ != diverting_to_stream_) + audio_manager_->RemoveOutputDeviceChangeListener(this); stream_->Stop(); stream_->Close(); + if (stream_ == diverting_to_stream_) + diverting_to_stream_ = NULL; stream_ = NULL; weak_this_.InvalidateWeakPtrs(); @@ -366,9 +371,6 @@ void AudioOutputController::DoStopCloseAndClearStream(WaitableEvent* done) { void AudioOutputController::OnDeviceChange() { DCHECK(message_loop_->BelongsToCurrentThread()); - // We should always have a stream by this point. - CHECK(stream_); - // Recreate the stream (DoCreate() will first shut down an existing stream). // Exit if we ran into an error. const State original_state = state_; @@ -393,4 +395,46 @@ void AudioOutputController::OnDeviceChange() { } } +const AudioParameters& AudioOutputController::GetAudioParameters() { + return params_; +} + +void AudioOutputController::StartDiverting(AudioOutputStream* to_stream) { + message_loop_->PostTask( + FROM_HERE, + base::Bind(&AudioOutputController::DoStartDiverting, this, to_stream)); +} + +void AudioOutputController::StopDiverting() { + message_loop_->PostTask( + FROM_HERE, base::Bind(&AudioOutputController::DoStopDiverting, this)); +} + +void AudioOutputController::DoStartDiverting(AudioOutputStream* to_stream) { + DCHECK(message_loop_->BelongsToCurrentThread()); + + if (state_ == kClosed) + return; + + DCHECK(!diverting_to_stream_); + diverting_to_stream_ = to_stream; + // Note: OnDeviceChange() will engage the "re-create" process, which will + // detect and use the alternate AudioOutputStream rather than create a new one + // via AudioManager. + OnDeviceChange(); +} + +void AudioOutputController::DoStopDiverting() { + DCHECK(message_loop_->BelongsToCurrentThread()); + + if (state_ == kClosed) + return; + + // Note: OnDeviceChange() will cause the existing stream (the consumer of the + // diverted audio data) to be closed, and diverting_to_stream_ will be set + // back to NULL. + OnDeviceChange(); + DCHECK(!diverting_to_stream_); +} + } // namespace media diff --git a/media/audio/audio_output_controller.h b/media/audio/audio_output_controller.h index 61718a3..21f8efe 100644 --- a/media/audio/audio_output_controller.h +++ b/media/audio/audio_output_controller.h @@ -12,6 +12,7 @@ #include "media/audio/audio_buffers_state.h" #include "media/audio/audio_io.h" #include "media/audio/audio_manager.h" +#include "media/audio/audio_source_diverter.h" #include "media/audio/simple_sources.h" #include "media/base/media_export.h" @@ -66,6 +67,7 @@ namespace media { class MEDIA_EXPORT AudioOutputController : public base::RefCountedThreadSafe<AudioOutputController>, public AudioOutputStream::AudioSourceCallback, + public AudioSourceDiverter, NON_EXPORTED_BASE(public AudioManager::AudioDeviceListener) { public: // An event handler that receives events from the AudioOutputController. The @@ -154,8 +156,13 @@ class MEDIA_EXPORT AudioOutputController // to being called. virtual void OnDeviceChange() OVERRIDE; + // AudioSourceDiverter implementation. + virtual const AudioParameters& GetAudioParameters() OVERRIDE; + virtual void StartDiverting(AudioOutputStream* to_stream) OVERRIDE; + virtual void StopDiverting() OVERRIDE; + protected: - // Internal state of the source. + // Internal state of the source. enum State { kEmpty, kCreated, @@ -188,6 +195,8 @@ class MEDIA_EXPORT AudioOutputController void DoClose(); void DoSetVolume(double volume); void DoReportError(int code); + void DoStartDiverting(AudioOutputStream* to_stream); + void DoStopDiverting(); // Helper method that starts physical stream. void StartStream(); @@ -197,6 +206,7 @@ class MEDIA_EXPORT AudioOutputController void DoStopCloseAndClearStream(base::WaitableEvent *done); AudioManager* const audio_manager_; + const AudioParameters params_; // |handler_| may be called only if |state_| is not kClosed. EventHandler* handler_; @@ -205,6 +215,9 @@ class MEDIA_EXPORT AudioOutputController // changed. See comment for weak_this_. AudioOutputStream* stream_; + // When non-NULL, audio is being diverted to this stream. + AudioOutputStream* diverting_to_stream_; + // The current volume of the audio stream. double volume_; @@ -227,8 +240,6 @@ class MEDIA_EXPORT AudioOutputController // Number of times left. int number_polling_attempts_left_; - AudioParameters params_; - // Used to auto-cancel the delayed tasks that are created to poll for data // (when starting-up a stream). base::WeakPtrFactory<AudioOutputController> weak_this_; diff --git a/media/audio/audio_output_controller_unittest.cc b/media/audio/audio_output_controller_unittest.cc index fe29ce5..ff37b5a 100644 --- a/media/audio/audio_output_controller_unittest.cc +++ b/media/audio/audio_output_controller_unittest.cc @@ -2,23 +2,24 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/basictypes.h" #include "base/bind.h" #include "base/environment.h" -#include "base/basictypes.h" #include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" #include "base/message_loop.h" #include "base/synchronization/waitable_event.h" #include "media/audio/audio_output_controller.h" +#include "media/audio/audio_parameters.h" +#include "media/base/audio_bus.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" -// TODO(vrk): These tests need to be rewritten! (crbug.com/112500) - using ::testing::_; using ::testing::AtLeast; using ::testing::DoAll; -using ::testing::Exactly; -using ::testing::InvokeWithoutArgs; +using ::testing::Invoke; using ::testing::NotNull; using ::testing::Return; @@ -27,9 +28,10 @@ namespace media { static const int kSampleRate = AudioParameters::kAudioCDSampleRate; static const int kBitsPerSample = 16; static const ChannelLayout kChannelLayout = CHANNEL_LAYOUT_STEREO; -static const int kSamplesPerPacket = kSampleRate / 10; +static const int kSamplesPerPacket = kSampleRate / 100; static const int kHardwareBufferSize = kSamplesPerPacket * ChannelLayoutToChannelCount(kChannelLayout) * kBitsPerSample / 8; +static const double kTestVolume = 0.25; class MockAudioOutputControllerEventHandler : public AudioOutputController::EventHandler { @@ -60,227 +62,321 @@ class MockAudioOutputControllerSyncReader DISALLOW_COPY_AND_ASSIGN(MockAudioOutputControllerSyncReader); }; +class MockAudioOutputStream : public AudioOutputStream { + public: + MOCK_METHOD0(Open, bool()); + MOCK_METHOD1(Start, void(AudioSourceCallback* callback)); + MOCK_METHOD0(Stop, void()); + MOCK_METHOD1(SetVolume, void(double volume)); + MOCK_METHOD1(GetVolume, void(double* volume)); + MOCK_METHOD0(Close, void()); + + // Set/get the callback passed to Start(). + AudioSourceCallback* callback() const { return callback_; } + void SetCallback(AudioSourceCallback* asc) { callback_ = asc; } + + private: + AudioSourceCallback* callback_; +}; + ACTION_P(SignalEvent, event) { event->Signal(); } -// Custom action to clear a memory buffer. -ACTION(ClearBuffer) { +static const float kBufferNonZeroData = 1.0f; +ACTION(PopulateBuffer) { arg1->Zero(); -} - -// Closes AudioOutputController synchronously. -static void CloseAudioController(AudioOutputController* controller) { - controller->Close(MessageLoop::QuitClosure()); - MessageLoop::current()->Run(); + // Note: To confirm the buffer will be populated in these tests, it's + // sufficient that only the first float in channel 0 is set to the value. + arg1->channel(0)[0] = kBufferNonZeroData; } class AudioOutputControllerTest : public testing::Test { public: - AudioOutputControllerTest() {} - virtual ~AudioOutputControllerTest() {} + AudioOutputControllerTest() + : audio_manager_(AudioManager::Create()), + create_event_(false, false), + play_event_(false, false), + read_event_(false, false), + pause_event_(false, false) { + } + + virtual ~AudioOutputControllerTest() { + } protected: - MessageLoopForIO message_loop_; + void Create(int samples_per_packet) { + EXPECT_FALSE(create_event_.IsSignaled()); + EXPECT_FALSE(play_event_.IsSignaled()); + EXPECT_FALSE(read_event_.IsSignaled()); + EXPECT_FALSE(pause_event_.IsSignaled()); + + params_ = AudioParameters( + AudioParameters::AUDIO_FAKE, kChannelLayout, + kSampleRate, kBitsPerSample, samples_per_packet); + + if (params_.IsValid()) { + EXPECT_CALL(mock_event_handler_, OnCreated(NotNull())) + .WillOnce(SignalEvent(&create_event_)); + } + + controller_ = AudioOutputController::Create( + audio_manager_.get(), &mock_event_handler_, params_, + &mock_sync_reader_); + if (controller_) + controller_->SetVolume(kTestVolume); + + EXPECT_EQ(params_.IsValid(), controller_ != NULL); + } + + void Play() { + // Expect the event handler to receive one OnPlaying() call. + EXPECT_CALL(mock_event_handler_, OnPlaying(NotNull())) + .WillOnce(SignalEvent(&play_event_)); + + // During playback, the mock pretends to provide audio data rendered and + // sent from the render process. + EXPECT_CALL(mock_sync_reader_, UpdatePendingBytes(_)) + .Times(AtLeast(2)); + EXPECT_CALL(mock_sync_reader_, Read(_, _)) + .WillRepeatedly(DoAll(PopulateBuffer(), + SignalEvent(&read_event_), + Return(params_.frames_per_buffer()))); + EXPECT_CALL(mock_sync_reader_, DataReady()) + .WillRepeatedly(Return(true)); + + controller_->Play(); + } + + void Pause() { + // Expect the event handler to receive one OnPaused() call. + EXPECT_CALL(mock_event_handler_, OnPaused(NotNull())) + .WillOnce(SignalEvent(&pause_event_)); + + controller_->Pause(); + } + + void ChangeDevice() { + // Expect the event handler to receive one OnPaying() call and no OnPaused() + // call. + EXPECT_CALL(mock_event_handler_, OnPlaying(NotNull())) + .WillOnce(SignalEvent(&play_event_)); + EXPECT_CALL(mock_event_handler_, OnPaused(NotNull())) + .Times(0); + + // Simulate a device change event to AudioOutputController from the + // AudioManager. + audio_manager_->GetMessageLoop()->PostTask( + FROM_HERE, + base::Bind(&AudioOutputController::OnDeviceChange, controller_)); + } + + void Divert(bool was_playing, bool will_be_playing) { + if (was_playing) { + // Expect the handler to receive one OnPlaying() call as a result of the + // stream switching. + EXPECT_CALL(mock_event_handler_, OnPlaying(NotNull())) + .WillOnce(SignalEvent(&play_event_)); + } + + EXPECT_CALL(mock_stream_, Open()) + .WillOnce(Return(true)); + EXPECT_CALL(mock_stream_, SetVolume(kTestVolume)); + if (will_be_playing) { + EXPECT_CALL(mock_stream_, Start(NotNull())) + .Times(AtLeast(1)) + .WillRepeatedly( + Invoke(&mock_stream_, &MockAudioOutputStream::SetCallback)); + } + // Always expect a Stop() call--even without a Start() call--since + // AudioOutputController likes to play it safe and Stop() before any + // Close(). + EXPECT_CALL(mock_stream_, Stop()) + .Times(AtLeast(1)); + + controller_->StartDiverting(&mock_stream_); + } + + void ReadDivertedAudioData() { + scoped_ptr<AudioBus> dest = AudioBus::Create(params_); + ASSERT_TRUE(!!mock_stream_.callback()); + const int frames_read = + mock_stream_.callback()->OnMoreData(dest.get(), AudioBuffersState()); + EXPECT_LT(0, frames_read); + EXPECT_EQ(kBufferNonZeroData, dest->channel(0)[0]); + } + + void Revert(bool was_playing) { + if (was_playing) { + // Expect the handler to receive one OnPlaying() call as a result of the + // stream switching back. + EXPECT_CALL(mock_event_handler_, OnPlaying(NotNull())) + .WillOnce(SignalEvent(&play_event_)); + } + + EXPECT_CALL(mock_stream_, Close()); + + controller_->StopDiverting(); + } + + void Close() { + EXPECT_CALL(mock_sync_reader_, Close()); + + controller_->Close(MessageLoop::QuitClosure()); + MessageLoop::current()->Run(); + } + + // These help make test sequences more readable. + void DivertNeverPlaying() { Divert(false, false); } + void DivertWillEventuallyBePlaying() { Divert(false, true); } + void DivertWhilePlaying() { Divert(true, true); } + void RevertWasNotPlaying() { Revert(false); } + void RevertWhilePlaying() { Revert(true); } + + // These synchronize the main thread with key events taking place on other + // threads. + void WaitForCreate() { create_event_.Wait(); } + void WaitForPlay() { play_event_.Wait(); } + void WaitForReads() { + // Note: Arbitrarily chosen, but more iterations causes tests to take + // significantly more time. + static const int kNumIterations = 3; + for (int i = 0; i < kNumIterations; ++i) { + read_event_.Wait(); + } + } + void WaitForPause() { pause_event_.Wait(); } private: + MessageLoopForIO message_loop_; + scoped_ptr<AudioManager> audio_manager_; + MockAudioOutputControllerEventHandler mock_event_handler_; + MockAudioOutputControllerSyncReader mock_sync_reader_; + MockAudioOutputStream mock_stream_; + base::WaitableEvent create_event_; + base::WaitableEvent play_event_; + base::WaitableEvent read_event_; + base::WaitableEvent pause_event_; + AudioParameters params_; + scoped_refptr<AudioOutputController> controller_; + DISALLOW_COPY_AND_ASSIGN(AudioOutputControllerTest); }; TEST_F(AudioOutputControllerTest, CreateAndClose) { - scoped_ptr<AudioManager> audio_manager(AudioManager::Create()); - if (!audio_manager->HasAudioOutputDevices()) - return; + Create(kSamplesPerPacket); + Close(); +} - MockAudioOutputControllerEventHandler event_handler; +TEST_F(AudioOutputControllerTest, HardwareBufferTooLarge) { + Create(kSamplesPerPacket * 1000); +} - EXPECT_CALL(event_handler, OnCreated(NotNull())) - .Times(1); +TEST_F(AudioOutputControllerTest, PlayAndClose) { + Create(kSamplesPerPacket); + WaitForCreate(); + Play(); + WaitForPlay(); + WaitForReads(); + Close(); +} - MockAudioOutputControllerSyncReader sync_reader; - EXPECT_CALL(sync_reader, Close()); +TEST_F(AudioOutputControllerTest, PlayPauseClose) { + Create(kSamplesPerPacket); + WaitForCreate(); + Play(); + WaitForPlay(); + WaitForReads(); + Pause(); + WaitForPause(); + Close(); +} - AudioParameters params(AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout, - kSampleRate, kBitsPerSample, kSamplesPerPacket); - scoped_refptr<AudioOutputController> controller = - AudioOutputController::Create( - audio_manager.get(), &event_handler, params, &sync_reader); - ASSERT_TRUE(controller.get()); +TEST_F(AudioOutputControllerTest, PlayPausePlayClose) { + Create(kSamplesPerPacket); + WaitForCreate(); + Play(); + WaitForPlay(); + WaitForReads(); + Pause(); + WaitForPause(); + Play(); + WaitForPlay(); + Close(); +} - // Close the controller immediately. - CloseAudioController(controller); +TEST_F(AudioOutputControllerTest, PlayDeviceChangeClose) { + Create(kSamplesPerPacket); + WaitForCreate(); + Play(); + WaitForPlay(); + WaitForReads(); + ChangeDevice(); + WaitForPlay(); + WaitForReads(); + Close(); } -TEST_F(AudioOutputControllerTest, PlayPauseClose) { - scoped_ptr<AudioManager> audio_manager(AudioManager::Create()); - if (!audio_manager->HasAudioOutputDevices()) - return; - - MockAudioOutputControllerEventHandler event_handler; - base::WaitableEvent event(false, false); - base::WaitableEvent pause_event(false, false); - - // If OnCreated is called then signal the event. - EXPECT_CALL(event_handler, OnCreated(NotNull())) - .WillOnce(InvokeWithoutArgs(&event, &base::WaitableEvent::Signal)); - - // OnPlaying() will be called only once. - EXPECT_CALL(event_handler, OnPlaying(NotNull())); - - MockAudioOutputControllerSyncReader sync_reader; - EXPECT_CALL(sync_reader, UpdatePendingBytes(_)) - .Times(AtLeast(2)); - EXPECT_CALL(sync_reader, Read(_, _)) - .WillRepeatedly(DoAll(ClearBuffer(), SignalEvent(&event), - Return(4))); - EXPECT_CALL(sync_reader, DataReady()) - .WillRepeatedly(Return(true)); - EXPECT_CALL(event_handler, OnPaused(NotNull())) - .WillOnce(InvokeWithoutArgs(&pause_event, &base::WaitableEvent::Signal)); - EXPECT_CALL(sync_reader, Close()); - - AudioParameters params(AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout, - kSampleRate, kBitsPerSample, kSamplesPerPacket); - scoped_refptr<AudioOutputController> controller = - AudioOutputController::Create( - audio_manager.get(), &event_handler, params, &sync_reader); - ASSERT_TRUE(controller.get()); - - // Wait for OnCreated() to be called. - event.Wait(); - - ASSERT_FALSE(pause_event.IsSignaled()); - controller->Play(); - controller->Pause(); - pause_event.Wait(); - - // Now stop the controller. - CloseAudioController(controller); +TEST_F(AudioOutputControllerTest, PlayDivertRevertClose) { + Create(kSamplesPerPacket); + WaitForCreate(); + Play(); + WaitForPlay(); + WaitForReads(); + DivertWhilePlaying(); + WaitForPlay(); + ReadDivertedAudioData(); + RevertWhilePlaying(); + WaitForPlay(); + WaitForReads(); + Close(); } -TEST_F(AudioOutputControllerTest, HardwareBufferTooLarge) { - scoped_ptr<AudioManager> audio_manager(AudioManager::Create()); - if (!audio_manager->HasAudioOutputDevices()) - return; - - // Create an audio device with a very large hardware buffer size. - MockAudioOutputControllerEventHandler event_handler; - - MockAudioOutputControllerSyncReader sync_reader; - AudioParameters params(AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout, - kSampleRate, kBitsPerSample, - kSamplesPerPacket * 1000); - scoped_refptr<AudioOutputController> controller = - AudioOutputController::Create( - audio_manager.get(), &event_handler, params, &sync_reader); - - // Use assert because we don't stop the device and assume we can't - // create one. - ASSERT_FALSE(controller); +TEST_F(AudioOutputControllerTest, PlayDivertRevertDivertRevertClose) { + Create(kSamplesPerPacket); + WaitForCreate(); + Play(); + WaitForPlay(); + WaitForReads(); + DivertWhilePlaying(); + WaitForPlay(); + ReadDivertedAudioData(); + RevertWhilePlaying(); + WaitForPlay(); + WaitForReads(); + DivertWhilePlaying(); + WaitForPlay(); + ReadDivertedAudioData(); + RevertWhilePlaying(); + WaitForPlay(); + WaitForReads(); + Close(); } -TEST_F(AudioOutputControllerTest, PlayPausePlayClose) { - scoped_ptr<AudioManager> audio_manager(AudioManager::Create()); - if (!audio_manager->HasAudioOutputDevices()) - return; - - MockAudioOutputControllerEventHandler event_handler; - base::WaitableEvent event(false, false); - EXPECT_CALL(event_handler, OnCreated(NotNull())) - .WillOnce(InvokeWithoutArgs(&event, &base::WaitableEvent::Signal)); - - // OnPlaying() will be called only once. - base::WaitableEvent play_event(false, false); - EXPECT_CALL(event_handler, OnPlaying(NotNull())) - .WillOnce(InvokeWithoutArgs(&play_event, &base::WaitableEvent::Signal)); - - // OnPaused() should never be called since the pause during kStarting is - // dropped when the second play comes in. - EXPECT_CALL(event_handler, OnPaused(NotNull())) - .Times(0); - - MockAudioOutputControllerSyncReader sync_reader; - EXPECT_CALL(sync_reader, UpdatePendingBytes(_)) - .Times(AtLeast(1)); - EXPECT_CALL(sync_reader, Read(_, _)) - .WillRepeatedly(DoAll(ClearBuffer(), SignalEvent(&event), Return(4))); - EXPECT_CALL(sync_reader, DataReady()) - .WillRepeatedly(Return(true)); - EXPECT_CALL(sync_reader, Close()); - - AudioParameters params(AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout, - kSampleRate, kBitsPerSample, kSamplesPerPacket); - scoped_refptr<AudioOutputController> controller = - AudioOutputController::Create( - audio_manager.get(), &event_handler, params, &sync_reader); - ASSERT_TRUE(controller.get()); - - // Wait for OnCreated() to be called. - event.Wait(); - - ASSERT_FALSE(play_event.IsSignaled()); - controller->Play(); - controller->Pause(); - controller->Play(); - play_event.Wait(); - - // Now stop the controller. - CloseAudioController(controller); +TEST_F(AudioOutputControllerTest, DivertPlayPausePlayRevertClose) { + Create(kSamplesPerPacket); + WaitForCreate(); + DivertWillEventuallyBePlaying(); + Play(); + WaitForPlay(); + ReadDivertedAudioData(); + Pause(); + WaitForPause(); + Play(); + WaitForPlay(); + ReadDivertedAudioData(); + RevertWhilePlaying(); + WaitForPlay(); + WaitForReads(); + Close(); } -// Ensure state change events are handled. -TEST_F(AudioOutputControllerTest, PlayStateChangeClose) { - scoped_ptr<AudioManager> audio_manager(AudioManager::Create()); - if (!audio_manager->HasAudioOutputDevices()) - return; - - MockAudioOutputControllerEventHandler event_handler; - base::WaitableEvent event(false, false); - EXPECT_CALL(event_handler, OnCreated(NotNull())) - .WillOnce(InvokeWithoutArgs(&event, &base::WaitableEvent::Signal)); - - // OnPlaying() will be called once normally and once after being recreated. - base::WaitableEvent play_event(false, false); - EXPECT_CALL(event_handler, OnPlaying(NotNull())) - .Times(2) - .WillRepeatedly(InvokeWithoutArgs( - &play_event, &base::WaitableEvent::Signal)); - - // OnPaused() should not be called during the state change event. - EXPECT_CALL(event_handler, OnPaused(NotNull())) - .Times(0); - - MockAudioOutputControllerSyncReader sync_reader; - EXPECT_CALL(sync_reader, UpdatePendingBytes(_)) - .Times(AtLeast(1)); - EXPECT_CALL(sync_reader, Read(_, _)) - .WillRepeatedly(DoAll(ClearBuffer(), SignalEvent(&event), Return(4))); - EXPECT_CALL(sync_reader, DataReady()) - .WillRepeatedly(Return(true)); - EXPECT_CALL(sync_reader, Close()); - - AudioParameters params(AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout, - kSampleRate, kBitsPerSample, kSamplesPerPacket); - scoped_refptr<AudioOutputController> controller = - AudioOutputController::Create( - audio_manager.get(), &event_handler, params, &sync_reader); - ASSERT_TRUE(controller.get()); - - // Wait for OnCreated() to be called. - event.Wait(); - - ASSERT_FALSE(play_event.IsSignaled()); - controller->Play(); - play_event.Wait(); - - // Force a state change and wait for the stream to come back to playing state. - play_event.Reset(); - audio_manager->GetMessageLoop()->PostTask(FROM_HERE, - base::Bind(&AudioOutputController::OnDeviceChange, controller)); - play_event.Wait(); - - // Now stop the controller. - CloseAudioController(controller); +TEST_F(AudioOutputControllerTest, DivertRevertClose) { + Create(kSamplesPerPacket); + WaitForCreate(); + DivertNeverPlaying(); + RevertWasNotPlaying(); + Close(); } } // namespace media diff --git a/media/audio/audio_source_diverter.h b/media/audio/audio_source_diverter.h new file mode 100644 index 0000000..787ddec --- /dev/null +++ b/media/audio/audio_source_diverter.h @@ -0,0 +1,40 @@ +// Copyright (c) 2013 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. + +#ifndef MEDIA_AUDIO_AUDIO_SOURCE_DIVERTER_H_ +#define MEDIA_AUDIO_AUDIO_SOURCE_DIVERTER_H_ + +#include "media/base/media_export.h" + +// Audio sources may optionally implement AudioSourceDiverter to temporarily +// divert audio data to an alternate AudioOutputStream. This allows the audio +// data to be plumbed to an alternate consumer; for example, a loopback +// mechanism for audio mirroring. + +namespace media { + +class AudioOutputStream; +class AudioParameters; + +class MEDIA_EXPORT AudioSourceDiverter { +public: + // Returns the audio parameters of the divertable audio data. + virtual const AudioParameters& GetAudioParameters() = 0; + + // Start providing audio data to the given |to_stream|, which is in an + // unopened state. |to_stream| remains under the control of the + // AudioSourceDiverter. + virtual void StartDiverting(AudioOutputStream* to_stream) = 0; + + // Stops diverting audio data to the stream. The AudioSourceDiverter is + // responsible for making sure the stream is closed, perhaps asynchronously. + virtual void StopDiverting() = 0; + +protected: + virtual ~AudioSourceDiverter() {} +}; + +} // namespace media + +#endif // MEDIA_AUDIO_AUDIO_SOURCE_DIVERTER_H_ diff --git a/media/media.gyp b/media/media.gyp index 05b35cc..feb1cc5 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -83,6 +83,7 @@ 'audio/audio_output_proxy.h', 'audio/audio_output_resampler.cc', 'audio/audio_output_resampler.h', + 'audio/audio_source_diverter.h', 'audio/audio_util.cc', 'audio/audio_util.h', 'audio/cross_process_notification.cc', |