summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormiu@chromium.org <miu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-01-11 09:31:47 +0000
committermiu@chromium.org <miu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-01-11 09:31:47 +0000
commitf8d1737f16939011ce55ab2a7ce438bc7172b87c (patch)
treefdfd957972bd74cfbe5c1122bd54a0449bf75ebd
parent98efbd9c6e1dd6db2a4f73eca2ce79ac77d3d0db (diff)
downloadchromium_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
-rw-r--r--content/browser/browser_main_loop.cc9
-rw-r--r--content/browser/browser_main_loop.h3
-rw-r--r--content/browser/renderer_host/media/audio_mirroring_manager.cc164
-rw-r--r--content/browser/renderer_host/media/audio_mirroring_manager.h108
-rw-r--r--content/browser/renderer_host/media/audio_mirroring_manager_unittest.cc234
-rw-r--r--content/browser/renderer_host/media/audio_renderer_host.cc41
-rw-r--r--content/browser/renderer_host/media/audio_renderer_host.h15
-rw-r--r--content/browser/renderer_host/media/audio_renderer_host_unittest.cc108
-rw-r--r--content/browser/renderer_host/render_process_host_impl.cc5
-rw-r--r--content/content_browser.gypi2
-rw-r--r--content/content_tests.gypi1
-rw-r--r--content/test/webrtc_audio_device_test.cc10
-rw-r--r--content/test/webrtc_audio_device_test.h2
-rw-r--r--media/audio/audio_output_controller.cc68
-rw-r--r--media/audio/audio_output_controller.h17
-rw-r--r--media/audio/audio_output_controller_unittest.cc490
-rw-r--r--media/audio/audio_source_diverter.h40
-rw-r--r--media/media.gyp1
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',