Chromium Code Reviews| 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..9ac9d1af8aaea6351fb82cfa982232253af1e5f7 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,127 @@ bool AudioRendererHost::OnMessageReceived(const IPC::Message& message, |
| return handled; |
| } |
| +// static |
| +AudioRendererHost* AudioRendererHost::FromRenderProcessID( |
| + int render_process_id) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| + |
| + 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; |
|
DaleCurtis
2012/12/05 23:35:14
Should this just CHECK() fail on missing entry?
miu
2012/12/11 02:30:45
Short answer: No, because it's possible for look-u
|
| +} |
| + |
| +// 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) { |
|
DaleCurtis
2012/12/05 23:35:14
CHECK() instead?
miu
2012/12/11 02:30:45
Explained above.
|
| + return; |
| + } |
| + |
| + // 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 |
|
DaleCurtis
2012/12/05 23:35:14
This seems strange. Are you saying callers can cha
miu
2012/12/11 02:30:45
Yes. Two ways to answer this:
1. Architecture: L
|
| + // 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); |
| + } 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) { |
|
DaleCurtis
2012/12/05 23:35:14
Same CHECK() comment.
miu
2012/12/11 02:30:45
Explained above.
|
| + return; |
| + } |
| + |
| + MirrorSessionMap::iterator session_it = |
| + host->mirror_sessions_.find(render_view_id); |
| + if (session_it == host->mirror_sessions_.end() || |
|
DaleCurtis
2012/12/05 23:35:14
Ditto.
miu
2012/12/11 02:30:45
This "if" check is necessary because StartMirrorin
|
| + 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); |
| + entry->controller->Revert(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 +438,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. |
| + |
| + // If this stream is currently being mirrored, remove it from that mirroring |
| + // session. |
| + MirrorSessionMap::const_iterator prev_session_it = |
| + mirror_sessions_.find(entry->render_view_id); |
| + if (prev_session_it != mirror_sessions_.end()) { |
| + if (entry->diverted_callback) { |
| + prev_session_it->second->RemoveInput(entry->diverted_callback); |
| + } |
| + } |
| + |
| + entry->render_view_id = render_view_id; |
| + |
| + // If a mirroring session is already active for the RenderView, add this |
| + // stream to that mirroring session. |
| + MirrorSessionMap::const_iterator next_session_it = |
| + mirror_sessions_.find(render_view_id); |
| + if (next_session_it != mirror_sessions_.end()) { |
| + if (!entry->pending_close) { |
| + if (!entry->diverted_callback) { |
| + entry->diverted_callback = entry->controller->Divert(); |
| + } |
| + next_session_it->second->AddInput(entry->diverted_callback, |
| + entry->controller->params()); |
| + } |
| + } else if (entry->diverted_callback) { |
| + // Clean-up: The audio stream was removed from the previous mirroring |
| + // session, but there is no other mirroring session for it to be added to. |
| + // So, "revert the divert." |
| + entry->controller->Revert(entry->diverted_callback); |
| + entry->diverted_callback = NULL; |
| + } |
| } |
| void AudioRendererHost::OnPlayStream(int stream_id) { |
| @@ -380,8 +571,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); |
| + // Note: We don't call AudioOutputController::Revert() since we're about |
| + // to close the controller below. If we did call Revert(), it would cause |
| + // an unnecessary round of immediate starting and stopping. |
| + } |
| + |
| entry->controller->Close( |
| base::Bind(&AudioRendererHost::DeleteEntry, this, entry)); |
| + entry->diverted_callback = NULL; |
| entry->pending_close = true; |
| } |
| } |