Index: content/browser/renderer_host/media/audio_renderer_host.cc |
diff --git a/content/browser/renderer_host/media/audio_renderer_host.cc b/content/browser/renderer_host/media/audio_renderer_host.cc |
index 83b5f2510baaa405905af5e07c561e76283b9ade..6a1c2b6367e597b282ba4d7ba8e77c58e127897b 100644 |
--- a/content/browser/renderer_host/media/audio_renderer_host.cc |
+++ b/content/browser/renderer_host/media/audio_renderer_host.cc |
@@ -12,6 +12,7 @@ |
#include "content/browser/renderer_host/media/audio_sync_reader.h" |
#include "content/common/media/audio_messages.h" |
#include "content/public/browser/media_observer.h" |
+#include "media/audio/audio_io.h" |
#include "media/audio/shared_memory_util.h" |
#include "media/base/audio_bus.h" |
#include "media/base/limits.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; |
@@ -37,27 +41,48 @@ struct AudioRendererHost::AudioEntry { |
// ownership of the reader. |
scoped_ptr<media::AudioOutputController::SyncReader> reader; |
+ // When non-NULL, normal audio output is being diverted for audio mirroring. |
+ media::AudioOutputStream::AudioSourceCallback* diverted_callback; |
+ |
// Set to true after we called Close() for the controller. |
bool pending_close; |
}; |
AudioRendererHost::AudioEntry::AudioEntry() |
: stream_id(0), |
+ render_view_id(MSG_ROUTING_NONE), |
+ diverted_callback(NULL), |
pending_close(false) { |
} |
-AudioRendererHost::AudioEntry::~AudioEntry() {} |
+AudioRendererHost::AudioEntry::~AudioEntry() { |
+ DCHECK(!diverted_callback); |
+} |
+ |
+AudioRendererHost::MirroringDestination::~MirroringDestination() {} |
/////////////////////////////////////////////////////////////////////////////// |
// AudioRendererHost implementations. |
AudioRendererHost::AudioRendererHost( |
+ int render_process_id, |
media::AudioManager* audio_manager, MediaObserver* media_observer) |
- : audio_manager_(audio_manager), |
+ : render_process_id_(render_process_id), |
+ audio_manager_(audio_manager), |
media_observer_(media_observer) { |
} |
AudioRendererHost::~AudioRendererHost() { |
DCHECK(audio_entries_.empty()); |
+ DCHECK(mirror_sessions_.empty()); |
+} |
+ |
+base::LazyInstance<AudioRendererHost::ActiveHostMap>::Leaky |
+ AudioRendererHost::g_host_map_ = LAZY_INSTANCE_INITIALIZER; |
+ |
+void AudioRendererHost::OnChannelConnected(int32 peer_pid) { |
+ g_host_map_.Get().insert(std::make_pair(render_process_id_, this)); |
+ |
+ BrowserMessageFilter::OnChannelConnected(peer_pid); |
} |
void AudioRendererHost::OnChannelClosing() { |
@@ -65,6 +90,13 @@ void AudioRendererHost::OnChannelClosing() { |
// Since the IPC channel is gone, close all requested audio streams. |
DeleteEntries(); |
+ |
+ while (!mirror_sessions_.empty()) { |
+ MirrorSessionMap::iterator it = mirror_sessions_.begin(); |
+ DoStopMirroring(render_process_id_, it->first, it->second); |
+ } |
+ |
+ g_host_map_.Get().erase(render_process_id_); |
} |
void AudioRendererHost::OnDestruct() const { |
@@ -216,6 +248,146 @@ bool AudioRendererHost::OnMessageReceived(const IPC::Message& message, |
return handled; |
} |
+// static |
+AudioRendererHost* AudioRendererHost::FromRenderProcessID( |
+ int render_process_id) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
+ |
+ // Note: It's possible for look-ups to fail since, for example, an |
+ // AudioRendererHost could be shutdown before StartMirroring() is called. |
+ // It's common for mirroring sessions to target a different render |
+ // process/view than the one that will receive the mirrored audio data. |
+ // Example sequence of events: |
+ // |
+ // 1. RenderProcess_1 wants to mirror a tab within RenderProcess_2. |
+ // 2. RenderProcess_1 starts sending IPCs to initiate a mirroring session. |
+ // 3. In the meantime, RenderProcess_2 is shut down (e.g., tab closed). The |
+ // AudioRendererHost for RenderProcess_2 is destroyed as a result. |
+ // 4. A browser-side entity for RenderProcess_1 attempts to call |
+ // StartMirroring(), but since the AudioRendererHost for RenderProcess_2 |
+ // is gone, the look-up here fails. |
+ |
+ ActiveHostMap& host_map = g_host_map_.Get(); |
+ ActiveHostMap::const_iterator it = host_map.find(render_process_id); |
+ return it == host_map.end() ? NULL : it->second; |
+} |
+ |
+// static |
+void AudioRendererHost::StartMirroring( |
+ int render_process_id, int render_view_id, |
+ const scoped_refptr<MirroringDestination>& destination) { |
+ BrowserThread::PostTask( |
+ BrowserThread::IO, FROM_HERE, |
+ base::Bind(&AudioRendererHost::DoStartMirroring, |
+ render_process_id, render_view_id, destination)); |
+} |
+ |
+// static |
+void AudioRendererHost::DoStartMirroring( |
+ int render_process_id, int render_view_id, |
+ const scoped_refptr<MirroringDestination>& destination) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
+ DCHECK(destination); |
+ |
+ AudioRendererHost* const host = FromRenderProcessID(render_process_id); |
+ if (!host) { |
+ return; // See comment in FromRenderProcessID(). |
+ } |
+ |
+ // 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. |
+ MirrorSessionMap::iterator session_it = |
+ host->mirror_sessions_.find(render_view_id); |
+ scoped_refptr<MirroringDestination> old_destination; |
+ if (session_it == host->mirror_sessions_.end()) { |
+ host->mirror_sessions_.insert(std::make_pair(render_view_id, 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 any existing audio outputs to |destination|. If streams were |
+ // already diverted to the |old_destination|, move them to the new |
+ // |destination|. |
+ for (AudioEntryMap::const_iterator it = host->audio_entries_.begin(); |
+ it != host->audio_entries_.end(); ++it) { |
+ AudioEntry* const entry = it->second; |
+ if (entry->render_view_id == render_view_id && !entry->pending_close) { |
+ if (old_destination) { |
+ DCHECK(entry->diverted_callback); |
+ old_destination->RemoveInput( |
+ entry->diverted_callback, |
+ base::Bind(&MirroringDestination::AddInput, destination, |
+ entry->diverted_callback, entry->controller->params())); |
+ } else { |
+ DCHECK(!entry->diverted_callback); |
+ entry->diverted_callback = entry->controller->Divert(); |
+ destination->AddInput(entry->diverted_callback, |
+ entry->controller->params()); |
+ } |
+ } |
+ } |
+} |
+ |
+// static |
+void AudioRendererHost::StopMirroring( |
+ int render_process_id, int render_view_id, |
+ const scoped_refptr<MirroringDestination>& destination) { |
+ BrowserThread::PostTask( |
+ BrowserThread::IO, FROM_HERE, |
+ base::Bind(&AudioRendererHost::DoStopMirroring, |
+ render_process_id, render_view_id, destination)); |
+} |
+ |
+// static |
+void AudioRendererHost::DoStopMirroring( |
+ int render_process_id, int render_view_id, |
+ const scoped_refptr<MirroringDestination>& destination) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
+ |
+ AudioRendererHost* const host = FromRenderProcessID(render_process_id); |
+ if (!host) { |
+ return; // See comment in FromRenderProcessID(). |
+ } |
+ |
+ MirrorSessionMap::iterator session_it = |
+ host->mirror_sessions_.find(render_view_id); |
+ if (session_it == host->mirror_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; |
+ |
+ // Revert the "divert" for each audio stream currently active in the mirroring |
+ // session being stopped. |
+ for (AudioEntryMap::const_iterator it = host->audio_entries_.begin(); |
+ it != host->audio_entries_.end(); ++it) { |
+ AudioEntry* const entry = it->second; |
+ if (entry->render_view_id == render_view_id && entry->diverted_callback) { |
+ destination->RemoveInput( |
+ entry->diverted_callback, |
+ base::Bind(&media::AudioOutputController::Revert, entry->controller, |
+ entry->diverted_callback)); |
+ entry->diverted_callback = NULL; |
+ } |
+ } |
+ |
+ host->mirror_sessions_.erase(session_it); |
+} |
+ |
void AudioRendererHost::OnCreateStream( |
int stream_id, const media::AudioParameters& params, int input_channels) { |
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
@@ -285,10 +457,48 @@ void AudioRendererHost::OnCreateStream( |
void AudioRendererHost::OnAssociateStreamWithProducer(int stream_id, |
int render_view_id) { |
- // TODO(miu): Will use render_view_id in upcoming change. |
- DVLOG(1) << "AudioRendererHost@" << this |
- << "::OnAssociateStreamWithProducer(stream_id=" << stream_id |
- << ", render_view_id=" << render_view_id << ")"; |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
+ |
+ AudioEntry* const entry = LookupById(stream_id); |
+ if (!entry) { |
+ SendErrorMessage(stream_id); |
+ return; |
+ } |
+ |
+ if (entry->render_view_id == render_view_id) |
+ return; // No change. |
+ |
+ MirrorSessionMap::const_iterator prev_session_it = |
+ mirror_sessions_.find(entry->render_view_id); |
+ entry->render_view_id = render_view_id; |
+ MirrorSessionMap::const_iterator next_session_it = |
+ mirror_sessions_.find(render_view_id); |
+ |
+ // Determine whether this stream was already part of a mirroring session, and |
+ // whether it should be participate in another mirroring session. |
+ if (prev_session_it != mirror_sessions_.end() && entry->diverted_callback) { |
+ if (next_session_it != mirror_sessions_.end() && !entry->pending_close) { |
+ // Stream was part of a previous mirroring session, and needs to be |
+ // transferred to a different one. |
+ prev_session_it->second->RemoveInput( |
+ entry->diverted_callback, |
+ base::Bind(&MirroringDestination::AddInput, next_session_it->second, |
+ entry->diverted_callback, entry->controller->params())); |
+ } else { |
+ // Stream is no longer participating in a mirroring session. |
+ prev_session_it->second->RemoveInput( |
+ entry->diverted_callback, |
+ base::Bind(&media::AudioOutputController::Revert, entry->controller, |
+ entry->diverted_callback)); |
+ entry->diverted_callback = NULL; |
+ } |
+ } else if (next_session_it != mirror_sessions_.end() && |
+ !entry->pending_close) { |
+ // Stream wasn't before, but is now participating in a mirroring session. |
+ entry->diverted_callback = entry->controller->Divert(); |
+ next_session_it->second->AddInput(entry->diverted_callback, |
+ entry->controller->params()); |
+ } |
} |
void AudioRendererHost::OnPlayStream(int stream_id) { |
@@ -380,8 +590,20 @@ void AudioRendererHost::CloseAndDeleteStream(AudioEntry* entry) { |
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
if (!entry->pending_close) { |
+ // If this stream is currently being mirrored, remove it from the mirroring |
+ // session. |
+ MirrorSessionMap::const_iterator session_it = |
+ mirror_sessions_.find(entry->render_view_id); |
+ if (session_it != mirror_sessions_.end() && entry->diverted_callback) { |
+ session_it->second->RemoveInput( |
+ entry->diverted_callback, |
+ base::Bind(&media::AudioOutputController::Revert, entry->controller, |
+ entry->diverted_callback)); |
+ } |
+ |
entry->controller->Close( |
base::Bind(&AudioRendererHost::DeleteEntry, this, entry)); |
+ entry->diverted_callback = NULL; |
entry->pending_close = true; |
} |
} |