Index: content/browser/renderer_host/media/web_contents_audio_input_stream.cc |
diff --git a/content/browser/renderer_host/media/web_contents_audio_input_stream.cc b/content/browser/renderer_host/media/web_contents_audio_input_stream.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..105f1efa37de1da564737dd9302ae43ace8e57a3 |
--- /dev/null |
+++ b/content/browser/renderer_host/media/web_contents_audio_input_stream.cc |
@@ -0,0 +1,280 @@ |
+// Copyright (c) 2012 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/web_contents_audio_input_stream.h" |
+ |
+#include <string> |
+ |
+#include "base/bind.h" |
+#include "base/callback_forward.h" |
+#include "base/string_number_conversions.h" |
+#include "base/string_piece.h" |
+#include "base/synchronization/lock.h" |
+#include "content/browser/renderer_host/media/audio_renderer_host.h" |
+#include "content/public/browser/browser_thread.h" |
+#include "content/public/browser/render_process_host.h" |
+#include "content/public/browser/render_view_host.h" |
+#include "content/public/browser/web_contents.h" |
+#include "content/public/browser/web_contents_observer.h" |
+#include "media/base/bind_to_loop.h" |
+ |
+namespace content { |
+ |
+// TODO(miu): Fix lots of code duplication, especially around the monitoring of |
+// WebContents and the parsing of device_ids. See |
+// web_contents_video_capture_device.cc. |
+ |
+//static |
+WebContentsAudioInputStream* WebContentsAudioInputStream::Create( |
+ const std::string& device_id) { |
+ // Parse device_id into render_process_id and render_view_id. |
+ const size_t sep_pos = device_id.find(':'); |
+ if (sep_pos == std::string::npos) { |
+ return NULL; |
+ } |
+ const base::StringPiece component1(device_id.data(), sep_pos); |
+ const base::StringPiece component2(device_id.data() + sep_pos + 1, |
+ device_id.length() - sep_pos - 1); |
+ int render_process_id = -1; |
+ int render_view_id = -1; |
+ if (!base::StringToInt(component1, &render_process_id) || |
+ !base::StringToInt(component2, &render_view_id)) { |
+ return NULL; |
+ } |
+ |
+ return new WebContentsAudioInputStream(render_process_id, render_view_id); |
+} |
+ |
+class WebContentsAudioInputStream::AudioRendererHostTracker |
Alpha Left Google
2012/11/20 21:49:43
I suggest we split this class out to a separate fi
miu
2012/11/21 08:27:48
Absolutely agree! :-) We think alike. I had alr
|
+ : public base::RefCountedThreadSafe<AudioRendererHostTracker>, |
+ public WebContentsObserver { |
+ public: |
+ typedef base::Callback<void(int render_process_id, int render_view_id)> |
+ ChangeCallback; |
+ |
+ void Start(int render_process_id, int render_view_id, |
+ const ChangeCallback& callback) { |
+ { |
+ base::AutoLock guard(lock_); |
Alpha Left Google
2012/11/20 21:49:43
There's no need to use a lock if you post tasks ba
miu
2012/11/21 08:27:48
The lock is needed for two reasons:
1. callback_.
|
+ callback_ = callback; |
+ } |
+ BrowserThread::PostTask( |
+ BrowserThread::UI, FROM_HERE, |
+ base::Bind(&AudioRendererHostTracker::LookUpWebAndObserveWebContents, |
+ this, |
+ render_process_id, render_view_id)); |
+ } |
+ |
+ void Stop() { |
+ { |
+ base::AutoLock guard(lock_); |
+ callback_.Reset(); |
+ } |
+ BrowserThread::PostTask( |
+ BrowserThread::UI, FROM_HERE, |
+ base::Bind(&AudioRendererHostTracker::Observe, this, |
+ static_cast<WebContents*>(NULL))); |
+ } |
+ |
+ private: |
+ friend class base::RefCountedThreadSafe<AudioRendererHostTracker>; |
+ |
+ virtual ~AudioRendererHostTracker() { |
+ DCHECK(!web_contents()) << "BUG: Still observering!"; |
+ } |
+ |
+ void LookUpWebAndObserveWebContents(int render_process_id, |
+ int render_view_id) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ |
+ RenderViewHost* const rvh = |
+ RenderViewHost::FromID(render_process_id, render_view_id); |
+ DVLOG_IF(1, !rvh) << "RenderViewHost::FromID(" |
+ << render_process_id << ", " << render_view_id |
+ << ") returned NULL."; |
+ Observe(rvh ? WebContents::FromRenderViewHost(rvh) : NULL); |
+ DVLOG_IF(1, !web_contents()) |
+ << "WebContents::FromRenderViewHost(" << rvh << ") returned NULL."; |
+ |
+ OnWebContentsChangeEvent(); |
+ } |
+ |
+ void OnWebContentsChangeEvent() { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ |
+ WebContents* const wc = web_contents(); |
+ RenderViewHost* const rvh = wc ? wc->GetRenderViewHost() : NULL; |
+ RenderProcessHost* const rph = rvh ? rvh->GetProcess() : NULL; |
+ |
+ const int render_process_id = rph ? rph->GetID() : MSG_ROUTING_NONE; |
+ const int render_view_id = rvh ? rvh->GetRoutingID() : MSG_ROUTING_NONE; |
+ |
+ base::AutoLock guard(lock_); |
+ if (!callback_.is_null()) { |
+ callback_.Run(render_process_id, render_view_id); |
Alpha Left Google
2012/11/20 21:49:43
WeakPtr doesn't work cross-threads. It has a DCHEC
miu
2012/11/21 08:27:48
callback_ is a base::Callback<...>, not a weak poi
|
+ } |
+ } |
+ |
+ // How WebContents notifies us of a new RenderView. |
+ virtual void RenderViewReady() OVERRIDE { |
+ OnWebContentsChangeEvent(); |
+ } |
+ // When a WebContents is destroyed. |
+ virtual void WebContentsDestroyed(WebContents* web_contents) OVERRIDE { |
+ OnWebContentsChangeEvent(); |
+ } |
+ |
+ base::Lock lock_; |
Alpha Left Google
2012/11/20 21:49:43
No need to use lock.
|
+ ChangeCallback callback_; |
+}; |
+ |
+ |
+WebContentsAudioInputStream::WebContentsAudioInputStream(int render_process_id, |
+ int render_view_id) |
+ : state_(kIdle), |
+ target_render_process_id_(render_process_id), |
+ target_render_view_id_(render_view_id) { |
+ target_host_ = |
+ AudioRendererHost::FromRenderProcessID(target_render_process_id_); |
+} |
+ |
+WebContentsAudioInputStream::~WebContentsAudioInputStream() { |
+ DCHECK(!tracker_) << "BUG: Close() not called after Open()."; |
+} |
+ |
+bool WebContentsAudioInputStream::Open() { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
+ |
+ if (state_ != kIdle) { |
+ return false; |
+ } |
+ |
+ DCHECK(!tracker_); |
+ tracker_ = new AudioRendererHostTracker(); |
+ const AudioRendererHostTracker::ChangeCallback& callback = |
+ media::BindToLoop( |
+ base::MessageLoopProxy::current(), |
+ base::Bind(&WebContentsAudioInputStream::OnTargetChanged, |
+ AsWeakPtr())); |
+ tracker_->Start(target_render_process_id_, target_render_view_id_, callback); |
+ |
+ return true; |
+} |
+ |
+void WebContentsAudioInputStream::Start(AudioInputCallback* callback) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
+ |
+ if (state_ != kIdle) { |
+ return; |
+ } |
+ |
+ state_ = kStarting; |
+ |
+ DCHECK(streams_.empty()); |
+ if (target_host_) { |
+ target_host_->StartMirroring(target_render_view_id_, this); |
+ } |
+ |
+ state_ = kRecording; |
+} |
+ |
+void WebContentsAudioInputStream::Stop() { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
+ |
+ if (state_ != kStarting && state_ != kRecording) { |
+ return; |
+ } |
+ |
+ state_ = kStopping; |
+ |
+ if (target_host_) { |
+ target_host_->StopMirroring(target_render_view_id_, this); |
Alpha Left Google
2012/11/20 21:49:43
Does this need |target_render_view_id_|? It seems
miu
2012/11/21 08:27:48
Answered in one of my comment responses in audio_r
|
+ } |
+ DCHECK(streams_.empty()); |
+ |
+ state_ = kIdle; |
+} |
+ |
+void WebContentsAudioInputStream::Close() { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
+ |
+ if (state_ != kIdle) { |
+ Stop(); |
+ } |
+ |
+ tracker_->Stop(); |
+ tracker_ = NULL; |
+ |
+ state_ = kClosed; |
+} |
+ |
+void WebContentsAudioInputStream::OnTargetChanged(int render_process_id, |
+ int render_view_id) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
+ |
+ if (target_render_process_id_ == render_process_id && |
+ target_render_view_id_ == render_view_id) { |
+ return; |
+ } |
+ |
+ if (state_ == kRecording) { |
+ if (target_host_) { |
+ target_host_->StopMirroring(target_render_view_id_, this); |
+ } |
+ DCHECK(streams_.empty()); |
+ state_ = kStarting; |
+ } |
+ |
+ target_render_process_id_ = render_process_id; |
+ target_render_view_id_ = render_view_id; |
+ target_host_ = AudioRendererHost::FromRenderProcessID(render_process_id); |
+ |
+ if (state_ == kStarting || state_ == kRecording) { |
+ DCHECK(streams_.empty()); |
+ if (target_host_) { |
+ target_host_->StartMirroring(target_render_view_id_, this); |
+ } |
+ state_ = kRecording; |
+ } |
+} |
+ |
+double WebContentsAudioInputStream::GetMaxVolume() { |
+ return 1.0; |
+} |
+ |
+void WebContentsAudioInputStream::SetVolume(double volume) { |
+ // no-op |
+} |
+ |
+double WebContentsAudioInputStream::GetVolume() { |
+ return 1.0; |
+} |
+ |
+void WebContentsAudioInputStream::SetAutomaticGainControl(bool enabled) { |
+ // no-op |
+} |
+ |
+bool WebContentsAudioInputStream::GetAutomaticGainControl() { |
+ return false; |
+} |
+ |
+void WebContentsAudioInputStream::AddAudioOutputStream( |
+ media::DivertedAudioOutputStream* aos) { |
+ DCHECK(!streams_.count(aos)) << "BUG: Adding same stream twice."; |
+ streams_.insert(aos); |
+ |
+ DVLOG(1) << "STUB: WebContentsAudioInputStream@" << this |
+ << "->AddAudioOutputStream(" << aos << ')'; |
+} |
+ |
+void WebContentsAudioInputStream::RemoveAudioOutputStream( |
+ media::DivertedAudioOutputStream* aos) { |
+ DCHECK(streams_.count(aos)) << "BUG: Removing unknown stream."; |
+ streams_.erase(aos); |
+ |
+ DVLOG(1) << "STUB: WebContentsAudioInputStream@" << this |
+ << "->RemoveAudioOutputStream(" << aos << ')'; |
+} |
+ |
+} // namespace content |