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..e2a2cecf6a3eb34ab6354e6c9663a9f80679ab79 |
| --- /dev/null |
| +++ b/content/browser/renderer_host/media/web_contents_audio_input_stream.cc |
| @@ -0,0 +1,347 @@ |
| +// Copyright (c) 2013 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/bind_helpers.h" |
| +#include "base/logging.h" |
| +#include "base/memory/scoped_ptr.h" |
| +#include "base/message_loop_proxy.h" |
| +#include "content/browser/browser_main_loop.h" |
| +#include "content/browser/renderer_host/media/audio_mirroring_manager.h" |
| +#include "content/browser/renderer_host/media/web_contents_capture_util.h" |
| +#include "content/public/browser/browser_thread.h" |
| +#include "media/audio/virtual_audio_input_stream.h" |
| +#include "media/audio/virtual_audio_output_stream.h" |
| + |
| +namespace content { |
| + |
| +class WebContentsAudioInputStream::Impl |
| + : public base::RefCountedThreadSafe<WebContentsAudioInputStream::Impl>, |
| + public AudioMirroringManager::MirroringDestination { |
| + public: |
| + // Takes ownership of |mixer_stream|. The rest outlive this instance. |
| + Impl(int render_process_id, int render_view_id, |
| + base::MessageLoopProxy* message_loop, |
| + AudioMirroringManager* mirroring_manager, |
| + const scoped_refptr<WebContentsTracker>& tracker, |
| + media::VirtualAudioInputStream* mixer_stream); |
| + |
| + // Open underlying VirtualAudioInputStream and start tracker. |
| + bool Open(); |
| + |
| + // Start the underlying VirtualAudioInputStream and instruct |
| + // AudioMirroringManager to begin a mirroring session. |
| + void Start(AudioInputCallback* callback); |
| + |
| + // Stop the underlying VirtualAudioInputStream and instruct |
| + // AudioMirroringManager to shutdown a mirroring session. |
| + void Stop(); |
| + |
| + // Close the underlying VirtualAudioInputStream and stop the tracker. |
| + void Close(); |
| + |
| + // Accessor to underlying VirtualAudioInputStream. |
| + media::VirtualAudioInputStream* mixer_stream() const { |
| + return mixer_stream_.get(); |
| + } |
| + |
| + private: |
| + friend class base::RefCountedThreadSafe<WebContentsAudioInputStream::Impl>; |
| + |
| + enum State { |
| + CONSTRUCTED, |
| + OPENED, |
| + MIRRORING, |
| + CLOSED |
| + }; |
| + |
| + virtual ~Impl(); |
| + |
| + // Returns true if the mirroring target has been permanently lost. |
| + bool IsTargetLost() const; |
| + |
| + // Notifies the consumer callback that the stream is now dead. |
| + void ReportError(); |
| + |
| + // Start/Stop mirroring by posting a call to AudioMirroringManager on the IO |
| + // BrowserThread. |
| + void StartMirroring(); |
| + void StopMirroring(); |
| + |
| + // AudioMirroringManager::MirroringDestination implementation |
| + virtual media::AudioOutputStream* AddInput( |
| + const media::AudioParameters& params) OVERRIDE; |
| + |
| + // Callback which is run when |stream| is closed. Deletes |stream|. |
| + void ReleaseInput(media::VirtualAudioOutputStream* stream); |
| + |
| + // Called by WebContentsTracker when the target of the audio mirroring has |
| + // changed. |
| + void OnTargetChanged(int render_process_id, int render_view_id); |
| + |
| + // Injected dependencies. |
| + base::MessageLoopProxy* const message_loop_; |
| + AudioMirroringManager* const mirroring_manager_; |
| + const scoped_refptr<WebContentsTracker> tracker_; |
| + // The AudioInputStream implementation that handles the audio conversion and |
| + // mixing details. |
| + const scoped_ptr<media::VirtualAudioInputStream> mixer_stream_; |
| + |
| + State state_; |
| + |
| + // Current audio mirroring target. |
| + int target_render_process_id_; |
| + int target_render_view_id_; |
| + |
| + // Current callback used to consume the resulting mixed audio data. |
| + AudioInputCallback* callback_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(Impl); |
| +}; |
| + |
| +WebContentsAudioInputStream::Impl::Impl( |
| + int render_process_id, int render_view_id, |
| + base::MessageLoopProxy* message_loop, |
| + AudioMirroringManager* mirroring_manager, |
| + const scoped_refptr<WebContentsTracker>& tracker, |
| + media::VirtualAudioInputStream* mixer_stream) |
| + : message_loop_(message_loop), mirroring_manager_(mirroring_manager), |
| + tracker_(tracker), mixer_stream_(mixer_stream), state_(CONSTRUCTED), |
| + target_render_process_id_(render_process_id), |
| + target_render_view_id_(render_view_id), |
| + callback_(NULL) { |
| + DCHECK(message_loop_); |
| + DCHECK(mirroring_manager_); |
| + DCHECK(tracker_); |
| + DCHECK(mixer_stream_.get()); |
| +} |
| + |
| +WebContentsAudioInputStream::Impl::~Impl() { |
| + DCHECK(state_ == CONSTRUCTED || state_ == CLOSED); |
| +} |
| + |
| +bool WebContentsAudioInputStream::Impl::Open() { |
| + DCHECK(message_loop_->BelongsToCurrentThread()); |
| + |
| + DCHECK_EQ(CONSTRUCTED, state_) << "Illegal to Open more than once."; |
| + |
| + if (!mixer_stream_->Open()) |
| + return false; |
| + |
| + state_ = OPENED; |
| + |
| + tracker_->Start( |
| + target_render_process_id_, target_render_view_id_, |
| + base::Bind(&Impl::OnTargetChanged, this)); |
| + |
| + return true; |
| +} |
| + |
| +void WebContentsAudioInputStream::Impl::Start(AudioInputCallback* callback) { |
| + DCHECK(message_loop_->BelongsToCurrentThread()); |
| + DCHECK(callback); |
| + |
| + if (state_ != OPENED) |
|
tommi (sloooow) - chröme
2013/01/15 10:58:42
DCHECK_EQ(state_, OPENED)?
|
| + return; |
| + |
| + if (IsTargetLost()) { |
| + ReportError(); |
| + return; |
| + } |
| + |
| + state_ = MIRRORING; |
| + |
| + callback_ = callback; |
| + mixer_stream_->Start(callback); |
| + |
| + StartMirroring(); |
| +} |
| + |
| +void WebContentsAudioInputStream::Impl::Stop() { |
| + DCHECK(message_loop_->BelongsToCurrentThread()); |
| + |
| + if (state_ != MIRRORING) |
| + return; |
| + |
| + state_ = OPENED; |
| + |
| + mixer_stream_->Stop(); |
| + callback_ = NULL; |
| + |
| + if (!IsTargetLost()) |
| + StopMirroring(); |
| +} |
| + |
| +void WebContentsAudioInputStream::Impl::Close() { |
| + DCHECK(message_loop_->BelongsToCurrentThread()); |
| + |
| + Stop(); |
| + |
| + if (state_ == OPENED) { |
| + state_ = CONSTRUCTED; |
| + tracker_->Stop(); |
| + mixer_stream_->Close(); |
| + } |
| + |
| + DCHECK_EQ(CONSTRUCTED, state_); |
| + state_ = CLOSED; |
| +} |
| + |
| +bool WebContentsAudioInputStream::Impl::IsTargetLost() const { |
| + DCHECK(message_loop_->BelongsToCurrentThread()); |
| + |
| + return target_render_process_id_ <= 0 || target_render_view_id_ <= 0; |
| +} |
| + |
| +void WebContentsAudioInputStream::Impl::ReportError() { |
| + DCHECK(message_loop_->BelongsToCurrentThread()); |
| + |
| + // TODO(miu): Need clean-up of AudioInputCallback interface in a future |
| + // change, since its only implementation ignores the first argument entirely |
| + // and the values for the second argument are undefined. |
| + callback_->OnError(NULL, 0); |
| +} |
| + |
| +void WebContentsAudioInputStream::Impl::StartMirroring() { |
| + DCHECK(message_loop_->BelongsToCurrentThread()); |
| + |
| + BrowserThread::PostTask( |
| + BrowserThread::IO, |
| + FROM_HERE, |
| + base::Bind(&AudioMirroringManager::StartMirroring, |
| + base::Unretained(mirroring_manager_), |
| + target_render_process_id_, target_render_view_id_, |
| + make_scoped_refptr(this))); |
| +} |
| + |
| +void WebContentsAudioInputStream::Impl::StopMirroring() { |
| + DCHECK(message_loop_->BelongsToCurrentThread()); |
| + |
| + BrowserThread::PostTask( |
| + BrowserThread::IO, |
| + FROM_HERE, |
| + base::Bind(&AudioMirroringManager::StopMirroring, |
| + base::Unretained(mirroring_manager_), |
| + target_render_process_id_, target_render_view_id_, |
| + make_scoped_refptr(this))); |
| +} |
| + |
| +media::AudioOutputStream* WebContentsAudioInputStream::Impl::AddInput( |
| + const media::AudioParameters& params) { |
| + media::VirtualAudioOutputStream* const stream = |
| + new media::VirtualAudioOutputStream( |
| + params, message_loop_, mixer_stream_.get()); |
| + // Note: The closure created here holds a reference to "this," which will |
| + // guarantee the VirtualAudioInputStream (mixer_stream_) outlives the |
| + // VirtualAudioOutputStream. |
| + stream->RunOnceClosed(base::Bind(&Impl::ReleaseInput, this, stream)); |
| + |
| + return stream; |
| +} |
| + |
| +void WebContentsAudioInputStream::Impl::ReleaseInput( |
| + media::VirtualAudioOutputStream* stream) { |
| + delete stream; |
| +} |
| + |
| +void WebContentsAudioInputStream::Impl::OnTargetChanged(int render_process_id, |
| + int render_view_id) { |
| + DCHECK(message_loop_->BelongsToCurrentThread()); |
| + |
| + if (target_render_process_id_ == render_process_id && |
| + target_render_view_id_ == render_view_id) |
| + return; |
| + |
| + DVLOG(1) << "Target RenderView has changed from " |
| + << target_render_process_id_ << ':' << target_render_view_id_ |
| + << " to " << render_process_id << ':' << render_view_id; |
| + |
| + if (state_ == MIRRORING) |
| + StopMirroring(); |
| + |
| + target_render_process_id_ = render_process_id; |
| + target_render_view_id_ = render_view_id; |
| + |
| + if (state_ == MIRRORING) { |
| + if (IsTargetLost()) { |
| + ReportError(); |
| + Stop(); |
| + } else { |
| + StartMirroring(); |
| + } |
| + } |
| +} |
| + |
| +// static |
| +WebContentsAudioInputStream* WebContentsAudioInputStream::Create( |
| + const std::string& device_id, |
| + const media::AudioParameters& params, |
| + base::MessageLoopProxy* message_loop) { |
| + int render_process_id; |
| + int render_view_id; |
| + if (!WebContentsCaptureUtil::ExtractTabCaptureTarget( |
| + device_id, &render_process_id, &render_view_id)) { |
| + return NULL; |
| + } |
| + |
| + return new WebContentsAudioInputStream( |
| + render_process_id, render_view_id, message_loop, |
| + BrowserMainLoop::GetAudioMirroringManager(), |
| + new WebContentsTracker(), |
| + new media::VirtualAudioInputStream(params, message_loop)); |
| +} |
| + |
| +WebContentsAudioInputStream::WebContentsAudioInputStream( |
| + int render_process_id, int render_view_id, |
| + base::MessageLoopProxy* message_loop, |
| + AudioMirroringManager* mirroring_manager, |
| + const scoped_refptr<WebContentsTracker>& tracker, |
| + media::VirtualAudioInputStream* mixer_stream) |
| + : impl_(new Impl(render_process_id, render_view_id, message_loop, |
| + mirroring_manager, tracker, mixer_stream)) {} |
| + |
| +WebContentsAudioInputStream::~WebContentsAudioInputStream() {} |
| + |
| +bool WebContentsAudioInputStream::Open() { |
| + return impl_->Open(); |
| +} |
| + |
| +void WebContentsAudioInputStream::Start(AudioInputCallback* callback) { |
| + impl_->Start(callback); |
| +} |
| + |
| +void WebContentsAudioInputStream::Stop() { |
| + impl_->Stop(); |
| +} |
| + |
| +void WebContentsAudioInputStream::Close() { |
| + impl_->Close(); |
| + delete this; |
| +} |
| + |
| +double WebContentsAudioInputStream::GetMaxVolume() { |
| + return impl_->mixer_stream()->GetMaxVolume(); |
| +} |
| + |
| +void WebContentsAudioInputStream::SetVolume(double volume) { |
| + impl_->mixer_stream()->SetVolume(volume); |
| +} |
| + |
| +double WebContentsAudioInputStream::GetVolume() { |
| + return impl_->mixer_stream()->GetVolume(); |
| +} |
| + |
| +void WebContentsAudioInputStream::SetAutomaticGainControl(bool enabled) { |
| + impl_->mixer_stream()->SetAutomaticGainControl(enabled); |
| +} |
| + |
| +bool WebContentsAudioInputStream::GetAutomaticGainControl() { |
| + return impl_->mixer_stream()->GetAutomaticGainControl(); |
| +} |
| + |
| +} // namespace content |