Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1064)

Unified Diff: content/browser/renderer_host/media/audio_renderer_host.cc

Issue 11413078: Tab Audio Capture: Browser-side connect/disconnect functionality. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Remove WCAudioInputStream changes (split into another change). Created 8 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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;
}
}

Powered by Google App Engine
This is Rietveld 408576698