| 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;
|
| }
|
| }
|
|
|