From 50f9789a2baac93fd5ac908ae01e4bec597955cf Mon Sep 17 00:00:00 2001 From: miu Date: Mon, 22 Sep 2014 15:49:52 -0700 Subject: [PATCH] WebContentsAudioMuter: Mute all audio output from a WebContentsImpl. Adds support in libcontent for muting all audio output from a WebContentsImpl. This is meant to be activated via user actions in the browser tab strip UI (follow-up change pending). To be clear, this change introduces the functionality required to mute audio, but there is nothing in this change to activate it. The plan is to provide this feature experimentally behind about:flags, and gather data to drive later launch decisions. Main approach: Introduce the WebContentsAudioMuter, which leverages the existing WebContents audio capture mechanisms (AudioMirroringManager) to divert all audio output to a "null" destination. In other words, this is essentially "audio capture to nowhere." BUG=360372 Review URL: https://codereview.chromium.org/586303004 Cr-Commit-Position: refs/heads/master@{#296078} --- .../capture/web_contents_audio_input_stream.cc | 20 +++ .../media/capture/web_contents_audio_muter.cc | 153 +++++++++++++++++++++ .../media/capture/web_contents_audio_muter.h | 43 ++++++ content/browser/web_contents/web_contents_impl.cc | 25 ++++ content/browser/web_contents/web_contents_impl.h | 6 + content/content_browser.gypi | 2 + content/public/browser/web_contents.h | 4 + 7 files changed, 253 insertions(+) create mode 100644 content/browser/media/capture/web_contents_audio_muter.cc create mode 100644 content/browser/media/capture/web_contents_audio_muter.h diff --git a/content/browser/media/capture/web_contents_audio_input_stream.cc b/content/browser/media/capture/web_contents_audio_input_stream.cc index 55eee222445a..c08b17ca82f6 100644 --- a/content/browser/media/capture/web_contents_audio_input_stream.cc +++ b/content/browser/media/capture/web_contents_audio_input_stream.cc @@ -74,6 +74,10 @@ class WebContentsAudioInputStream::Impl void StartMirroring(); void StopMirroring(); + // Invoked on the UI thread to make sure WebContents muting is turned off for + // successful audio capture. + void UnmuteWebContentsAudio(); + // AudioMirroringManager::MirroringDestination implementation virtual void QueryForMatches( const std::set& candidates, @@ -174,6 +178,14 @@ void WebContentsAudioInputStream::Impl::Start(AudioInputCallback* callback) { mixer_stream_->Start(callback); StartMirroring(); + + // WebContents audio muting is implemented as audio capture to nowhere. + // Unmuting will stop that audio capture, allowing AudioMirroringManager to + // divert audio capture to here. + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind(&Impl::UnmuteWebContentsAudio, this)); } void WebContentsAudioInputStream::Impl::Stop() { @@ -235,6 +247,14 @@ void WebContentsAudioInputStream::Impl::StopMirroring() { make_scoped_refptr(this))); } +void WebContentsAudioInputStream::Impl::UnmuteWebContentsAudio() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + + WebContents* const contents = tracker_->web_contents(); + if (contents) + contents->SetAudioMuted(false); +} + void WebContentsAudioInputStream::Impl::QueryForMatches( const std::set& candidates, const MatchesCallback& results_callback) { diff --git a/content/browser/media/capture/web_contents_audio_muter.cc b/content/browser/media/capture/web_contents_audio_muter.cc new file mode 100644 index 000000000000..461883561410 --- /dev/null +++ b/content/browser/media/capture/web_contents_audio_muter.cc @@ -0,0 +1,153 @@ +// Copyright 2014 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/media/capture/web_contents_audio_muter.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "content/browser/media/capture/audio_mirroring_manager.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/web_contents.h" +#include "media/audio/audio_io.h" +#include "media/audio/audio_manager.h" +#include "media/audio/fake_audio_consumer.h" +#include "media/base/bind_to_current_loop.h" + +namespace content { + +namespace { + +// An AudioOutputStream that pumps audio data, but does nothing with it. +// Pumping the audio data is necessary because video playback is synchronized to +// the audio stream and will freeze otherwise. +// +// TODO(miu): media::FakeAudioOutputStream does pretty much the same thing as +// this class, but requires construction/destruction via media::AudioManagerBase +// on the audio thread. Once that's fixed, this class will no longer be needed. +// http://crbug.com/416278 +class AudioDiscarder : public media::AudioOutputStream { + public: + explicit AudioDiscarder(const media::AudioParameters& params) + : consumer_(media::AudioManager::Get()->GetWorkerTaskRunner(), params) {} + + // AudioOutputStream implementation. + virtual bool Open() OVERRIDE { return true; } + virtual void Start(AudioSourceCallback* callback) OVERRIDE { + consumer_.Start(base::Bind(&AudioDiscarder::FetchAudioData, callback)); + } + virtual void Stop() OVERRIDE { consumer_.Stop(); } + virtual void SetVolume(double volume) OVERRIDE {} + virtual void GetVolume(double* volume) OVERRIDE { *volume = 0; } + virtual void Close() OVERRIDE { delete this; } + + private: + virtual ~AudioDiscarder() {} + + static void FetchAudioData(AudioSourceCallback* callback, + media::AudioBus* audio_bus) { + callback->OnMoreData(audio_bus, media::AudioBuffersState()); + } + + // Calls FetchAudioData() at regular intervals and discards the data. + media::FakeAudioConsumer consumer_; + + DISALLOW_COPY_AND_ASSIGN(AudioDiscarder); +}; + +} // namespace + +// A simple AudioMirroringManager::MirroringDestination implementation that +// identifies the audio streams rendered by a WebContents and provides +// AudioDiscarders to AudioMirroringManager. +class WebContentsAudioMuter::MuteDestination + : public base::RefCountedThreadSafe, + public AudioMirroringManager::MirroringDestination { + public: + explicit MuteDestination(WebContents* web_contents) + : web_contents_(web_contents) {} + + private: + friend class base::RefCountedThreadSafe; + + typedef AudioMirroringManager::SourceFrameRef SourceFrameRef; + + virtual ~MuteDestination() {} + + virtual void QueryForMatches( + const std::set& candidates, + const MatchesCallback& results_callback) OVERRIDE { + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind(&MuteDestination::QueryForMatchesOnUIThread, + this, + candidates, + media::BindToCurrentLoop(results_callback))); + } + + void QueryForMatchesOnUIThread(const std::set& candidates, + const MatchesCallback& results_callback) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + std::set matches; + // Add each ID to |matches| if it maps to a RenderFrameHost that maps to the + // WebContents being muted. + for (std::set::const_iterator i = candidates.begin(); + i != candidates.end(); ++i) { + WebContents* const contents_containing_frame = + WebContents::FromRenderFrameHost( + RenderFrameHost::FromID(i->first, i->second)); + if (contents_containing_frame == web_contents_) + matches.insert(*i); + } + results_callback.Run(matches); + } + + virtual media::AudioOutputStream* AddInput( + const media::AudioParameters& params) OVERRIDE { + return new AudioDiscarder(params); + } + + WebContents* const web_contents_; + + DISALLOW_COPY_AND_ASSIGN(MuteDestination); +}; + +WebContentsAudioMuter::WebContentsAudioMuter(WebContents* web_contents) + : destination_(new MuteDestination(web_contents)), is_muting_(false) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); +} + +WebContentsAudioMuter::~WebContentsAudioMuter() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + StopMuting(); +} + +void WebContentsAudioMuter::StartMuting() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (is_muting_) + return; + is_muting_ = true; + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(&AudioMirroringManager::StartMirroring, + base::Unretained(AudioMirroringManager::GetInstance()), + destination_)); +} + +void WebContentsAudioMuter::StopMuting() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (!is_muting_) + return; + is_muting_ = false; + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(&AudioMirroringManager::StopMirroring, + base::Unretained(AudioMirroringManager::GetInstance()), + destination_)); +} + +} // namespace content diff --git a/content/browser/media/capture/web_contents_audio_muter.h b/content/browser/media/capture/web_contents_audio_muter.h new file mode 100644 index 000000000000..d9c5f2fcbb2a --- /dev/null +++ b/content/browser/media/capture/web_contents_audio_muter.h @@ -0,0 +1,43 @@ +// Copyright 2014 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. + +#ifndef CONTENT_BROWSER_MEDIA_CAPTURE_WEB_CONTENTS_AUDIO_MUTER_H_ +#define CONTENT_BROWSER_MEDIA_CAPTURE_WEB_CONTENTS_AUDIO_MUTER_H_ + +#include "base/memory/ref_counted.h" + +namespace content { + +class WebContents; + +// Mutes all audio output from a WebContents. Internally, this is accomplished +// by providing a MirroringDestination implementation, similar to that found in +// WebContentsAudioInputStream for audio capture/mirroring. However, the +// WebContentsAudioMuter::MuteDestination only pumps the audio data and discards +// it. +class WebContentsAudioMuter { + public: + explicit WebContentsAudioMuter(WebContents* web_contents); + ~WebContentsAudioMuter(); + + bool is_muting() const { return is_muting_; } + + void StartMuting(); + void StopMuting(); + + private: + // AudioMirroringManager::MirroringDestination implementation which is + // ref-counted so it remains alive as tasks referencing it are posted on both + // the UI and IO threads. + class MuteDestination; + const scoped_refptr destination_; + + bool is_muting_; + + DISALLOW_COPY_AND_ASSIGN(WebContentsAudioMuter); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_MEDIA_CAPTURE_WEB_CONTENTS_AUDIO_MUTER_H_ diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc index 14cf8a42cbf2..592a72af6dd9 100644 --- a/content/browser/web_contents/web_contents_impl.cc +++ b/content/browser/web_contents/web_contents_impl.cc @@ -40,6 +40,7 @@ #include "content/browser/loader/resource_dispatcher_host_impl.h" #include "content/browser/manifest/manifest_manager_host.h" #include "content/browser/media/audio_stream_monitor.h" +#include "content/browser/media/capture/web_contents_audio_muter.h" #include "content/browser/media/midi_dispatcher_host.h" #include "content/browser/message_port_message_filter.h" #include "content/browser/message_port_service.h" @@ -987,6 +988,30 @@ int WebContentsImpl::GetCapturerCount() const { return capturer_count_; } +bool WebContentsImpl::IsAudioMuted() const { + return audio_muter_.get() && audio_muter_->is_muting(); +} + +void WebContentsImpl::SetAudioMuted(bool mute) { + DVLOG(1) << "SetAudioMuted(mute=" << mute << "), was " << IsAudioMuted() + << " for WebContentsImpl@" << this; + + if (mute == IsAudioMuted()) + return; + + if (mute) { + if (!audio_muter_) + audio_muter_.reset(new WebContentsAudioMuter(this)); + audio_muter_->StartMuting(); + } else { + DCHECK(audio_muter_); + audio_muter_->StopMuting(); + } + + // Notification for UI updates in response to the changed muting state. + NotifyNavigationStateChanged(INVALIDATE_TYPE_TAB); +} + bool WebContentsImpl::IsCrashed() const { return (crashed_status_ == base::TERMINATION_STATUS_PROCESS_CRASHED || crashed_status_ == base::TERMINATION_STATUS_ABNORMAL_TERMINATION || diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h index 134b8d8860b4..5c76a984d54e 100644 --- a/content/browser/web_contents/web_contents_impl.h +++ b/content/browser/web_contents/web_contents_impl.h @@ -65,6 +65,7 @@ class SavePackage; class ScreenOrientationDispatcherHost; class SiteInstance; class TestWebContents; +class WebContentsAudioMuter; class WebContentsDelegate; class WebContentsImpl; class WebContentsObserver; @@ -246,6 +247,8 @@ class CONTENT_EXPORT WebContentsImpl virtual void IncrementCapturerCount(const gfx::Size& capture_size) OVERRIDE; virtual void DecrementCapturerCount() OVERRIDE; virtual int GetCapturerCount() const OVERRIDE; + virtual bool IsAudioMuted() const OVERRIDE; + virtual void SetAudioMuted(bool mute) OVERRIDE; virtual bool IsCrashed() const OVERRIDE; virtual void SetIsCrashed(base::TerminationStatus status, int error_code) OVERRIDE; @@ -1241,6 +1244,9 @@ class CONTENT_EXPORT WebContentsImpl // Monitors power levels for audio streams associated with this WebContents. AudioStreamMonitor audio_stream_monitor_; + // Created on-demand to mute all audio output from this WebContents. + scoped_ptr audio_muter_; + base::WeakPtrFactory loading_weak_factory_; DISALLOW_COPY_AND_ASSIGN(WebContentsImpl); diff --git a/content/content_browser.gypi b/content/content_browser.gypi index d0a891f2a926..49cfd02d7ab5 100644 --- a/content/content_browser.gypi +++ b/content/content_browser.gypi @@ -849,6 +849,8 @@ 'browser/media/capture/video_capture_oracle.h', 'browser/media/capture/web_contents_audio_input_stream.cc', 'browser/media/capture/web_contents_audio_input_stream.h', + 'browser/media/capture/web_contents_audio_muter.cc', + 'browser/media/capture/web_contents_audio_muter.h', 'browser/media/capture/web_contents_capture_util.cc', 'browser/media/capture/web_contents_capture_util.h', 'browser/media/capture/web_contents_tracker.cc', diff --git a/content/public/browser/web_contents.h b/content/public/browser/web_contents.h index b62e9546d386..4f0d625767a8 100644 --- a/content/public/browser/web_contents.h +++ b/content/public/browser/web_contents.h @@ -306,6 +306,10 @@ class WebContents : public PageNavigator, virtual void DecrementCapturerCount() = 0; virtual int GetCapturerCount() const = 0; + // Indicates/Sets whether all audio output from this WebContents is muted. + virtual bool IsAudioMuted() const = 0; + virtual void SetAudioMuted(bool mute) = 0; + // Indicates whether this tab should be considered crashed. The setter will // also notify the delegate when the flag is changed. virtual bool IsCrashed() const = 0; -- 2.11.4.GIT