Chromium Code Reviews| 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 |