Commit 2ebc174f authored by Olga Sharonova's avatar Olga Sharonova Committed by Commit Bot

Loopback and muting in the forwarding factory

Muting:
* There is an AudioSessionMuter class which manages muting for a group.
* InputStreamBroker has one in case source should be muted.
* ForwardingFactory also has one if webcontents should be muted.

Bug: 824019
Change-Id: I41c26e8de38912f9dac1409ff9dba86cd43631ea
Reviewed-on: https://chromium-review.googlesource.com/1046052
Commit-Queue: Olga Sharonova <olka@chromium.org>
Reviewed-by: default avatarMax Morin <maxmorin@chromium.org>
Reviewed-by: default avatarNasko Oskov <nasko@chromium.org>
Reviewed-by: default avatarYuri Wiitala <miu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#558671}
parent af74a462
......@@ -1059,6 +1059,10 @@ jumbo_source_set("browser") {
"media/audible_metrics.h",
"media/audio_input_stream_broker.cc",
"media/audio_input_stream_broker.h",
"media/audio_loopback_stream_broker.cc",
"media/audio_loopback_stream_broker.h",
"media/audio_muting_session.cc",
"media/audio_muting_session.h",
"media/audio_output_stream_broker.cc",
"media/audio_output_stream_broker.h",
"media/audio_stream_broker.cc",
......
......@@ -68,6 +68,7 @@ class MockRendererAudioInputStreamFactoryClient
mojo::Binding<mojom::RendererAudioInputStreamFactoryClient> binding_;
media::mojom::AudioInputStreamPtr input_stream_;
media::mojom::AudioInputStreamClientRequest client_request_;
DISALLOW_COPY_AND_ASSIGN(MockRendererAudioInputStreamFactoryClient);
};
class MockStreamFactory : public audio::FakeStreamFactory {
......@@ -128,6 +129,7 @@ class MockStreamFactory : public audio::FakeStreamFactory {
}
StreamRequestData* stream_request_data_;
DISALLOW_COPY_AND_ASSIGN(MockStreamFactory);
};
struct TestEnvironment {
......
// Copyright 2018 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/audio_loopback_stream_broker.h"
#include <utility>
#include "base/unguessable_token.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_process_host.h"
namespace content {
AudioStreamBrokerFactory::LoopbackSource::LoopbackSource() = default;
AudioStreamBrokerFactory::LoopbackSource::LoopbackSource(
WebContents* source_contents)
: WebContentsObserver(source_contents) {
DCHECK(source_contents);
}
AudioStreamBrokerFactory::LoopbackSource::~LoopbackSource() = default;
base::UnguessableToken AudioStreamBrokerFactory::LoopbackSource::GetGroupID() {
if (WebContentsImpl* source_contents =
static_cast<WebContentsImpl*>(web_contents())) {
return source_contents->GetAudioStreamFactory()->group_id();
}
return base::UnguessableToken();
}
void AudioStreamBrokerFactory::LoopbackSource::OnStartCapturing() {
if (WebContentsImpl* source_contents =
static_cast<WebContentsImpl*>(web_contents())) {
source_contents->IncrementCapturerCount(gfx::Size());
}
}
void AudioStreamBrokerFactory::LoopbackSource::OnStopCapturing() {
if (WebContentsImpl* source_contents =
static_cast<WebContentsImpl*>(web_contents())) {
source_contents->DecrementCapturerCount();
}
}
void AudioStreamBrokerFactory::LoopbackSource::WebContentsDestroyed() {
if (on_gone_closure_)
std::move(on_gone_closure_).Run();
}
AudioLoopbackStreamBroker::AudioLoopbackStreamBroker(
int render_process_id,
int render_frame_id,
std::unique_ptr<AudioStreamBrokerFactory::LoopbackSource> source,
const media::AudioParameters& params,
uint32_t shared_memory_count,
bool mute_source,
AudioStreamBroker::DeleterCallback deleter,
mojom::RendererAudioInputStreamFactoryClientPtr renderer_factory_client)
: AudioStreamBroker(render_process_id, render_frame_id),
source_(std::move(source)),
params_(params),
shared_memory_count_(shared_memory_count),
deleter_(std::move(deleter)),
renderer_factory_client_(std::move(renderer_factory_client)),
observer_binding_(this),
weak_ptr_factory_(this) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(source_);
DCHECK(source_->GetGroupID());
DCHECK(renderer_factory_client_);
DCHECK(deleter_);
// Unretained is safe because |this| owns |source_|.
source_->set_on_gone_closure(base::BindOnce(
&AudioLoopbackStreamBroker::Cleanup, base::Unretained(this)));
if (mute_source) {
muter_.emplace(source_->GetGroupID());
}
// Unretained is safe because |this| owns |renderer_factory_client_|.
renderer_factory_client_.set_connection_error_handler(base::BindOnce(
&AudioLoopbackStreamBroker::Cleanup, base::Unretained(this)));
// Notify the source that we are capturing from it, to prevent its
// backgrounding.
source_->OnStartCapturing();
// Notify RenderProcessHost about the input stream, so that the destination
// renderer does not get background.
if (auto* process_host = RenderProcessHost::FromID(render_process_id))
process_host->OnMediaStreamAdded();
}
AudioLoopbackStreamBroker::~AudioLoopbackStreamBroker() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
source_->OnStopCapturing();
if (auto* process_host = RenderProcessHost::FromID(render_process_id()))
process_host->OnMediaStreamRemoved();
}
void AudioLoopbackStreamBroker::CreateStream(
audio::mojom::StreamFactory* factory) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!observer_binding_.is_bound());
DCHECK(!client_request_);
DCHECK(source_->GetGroupID());
if (muter_) // Mute the source.
muter_->Connect(factory);
media::mojom::AudioInputStreamClientPtr client;
client_request_ = mojo::MakeRequest(&client);
media::mojom::AudioInputStreamPtr stream;
media::mojom::AudioInputStreamRequest stream_request =
mojo::MakeRequest(&stream);
media::mojom::AudioInputStreamObserverPtr observer_ptr;
observer_binding_.Bind(mojo::MakeRequest(&observer_ptr));
// Unretained is safe because |this| owns |observer_binding_|.
observer_binding_.set_connection_error_handler(base::BindOnce(
&AudioLoopbackStreamBroker::Cleanup, base::Unretained(this)));
factory->CreateLoopbackStream(
std::move(stream_request), std::move(client), std::move(observer_ptr),
params_, shared_memory_count_, source_->GetGroupID(),
base::BindOnce(&AudioLoopbackStreamBroker::StreamCreated,
weak_ptr_factory_.GetWeakPtr(), std::move(stream)));
}
void AudioLoopbackStreamBroker::DidStartRecording() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
void AudioLoopbackStreamBroker::StreamCreated(
media::mojom::AudioInputStreamPtr stream,
media::mojom::AudioDataPipePtr data_pipe) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!data_pipe) {
Cleanup();
return;
}
DCHECK(renderer_factory_client_);
renderer_factory_client_->StreamCreated(
std::move(stream), std::move(client_request_), std::move(data_pipe),
false /* |initially_muted|: Loopback streams are never muted. */);
}
void AudioLoopbackStreamBroker::Cleanup() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
std::move(deleter_).Run(this);
}
} // namespace content
// Copyright 2018 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_AUDIO_LOOPBACK_STREAM_BROKER_H_
#define CONTENT_BROWSER_MEDIA_AUDIO_LOOPBACK_STREAM_BROKER_H_
#include <string>
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "base/sequence_checker.h"
#include "content/browser/media/audio_muting_session.h"
#include "content/browser/media/audio_stream_broker.h"
#include "content/common/content_export.h"
#include "content/common/media/renderer_audio_input_stream_factory.mojom.h"
#include "media/base/audio_parameters.h"
#include "media/mojo/interfaces/audio_input_stream.mojom.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "services/audio/public/mojom/stream_factory.mojom.h"
namespace content {
// AudioLoopbackStreamBroker is used to broker a connection between a client
// (typically renderer) and the audio service. It is operated on the UI thread.
class CONTENT_EXPORT AudioLoopbackStreamBroker final
: public AudioStreamBroker,
public media::mojom::AudioInputStreamObserver {
public:
AudioLoopbackStreamBroker(
int render_process_id,
int render_frame_id,
std::unique_ptr<AudioStreamBrokerFactory::LoopbackSource> source,
const media::AudioParameters& params,
uint32_t shared_memory_count,
bool mute_source,
AudioStreamBroker::DeleterCallback deleter,
mojom::RendererAudioInputStreamFactoryClientPtr renderer_factory_client);
~AudioLoopbackStreamBroker() final;
// Creates the stream.
void CreateStream(audio::mojom::StreamFactory* factory) final;
// media::AudioInputStreamObserver implementation.
void DidStartRecording() final;
private:
void StreamCreated(media::mojom::AudioInputStreamPtr stream,
media::mojom::AudioDataPipePtr data_pipe);
void Cleanup();
const std::unique_ptr<AudioStreamBrokerFactory::LoopbackSource> source_;
const media::AudioParameters params_;
const uint32_t shared_memory_count_;
DeleterCallback deleter_;
// Constructed only if the loopback source playback should be muted while the
// loopback stream is running.
base::Optional<AudioMutingSession> muter_;
mojom::RendererAudioInputStreamFactoryClientPtr renderer_factory_client_;
mojo::Binding<AudioInputStreamObserver> observer_binding_;
media::mojom::AudioInputStreamClientRequest client_request_;
base::WeakPtrFactory<AudioLoopbackStreamBroker> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(AudioLoopbackStreamBroker);
};
} // namespace content
#endif // CONTENT_BROWSER_MEDIA_AUDIO_LOOPBACK_STREAM_BROKER_H_
This diff is collapsed.
// Copyright 2018 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/audio_muting_session.h"
namespace content {
AudioMutingSession::AudioMutingSession(const base::UnguessableToken& group_id)
: group_id_(group_id) {}
AudioMutingSession::~AudioMutingSession(){};
void AudioMutingSession::Connect(audio::mojom::StreamFactory* factory) {
if (muter_)
muter_.reset();
DCHECK(factory);
factory->BindMuter(mojo::MakeRequest(&muter_), group_id_);
}
} // namespace content
// Copyright 2018 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_AUDIO_MUTING_SESSION_H_
#define CONTENT_BROWSER_MEDIA_AUDIO_MUTING_SESSION_H_
#include <utility>
#include "base/unguessable_token.h"
#include "content/common/content_export.h"
#include "services/audio/public/mojom/stream_factory.mojom.h"
namespace content {
class CONTENT_EXPORT AudioMutingSession {
public:
explicit AudioMutingSession(const base::UnguessableToken& group_id);
~AudioMutingSession();
void Connect(audio::mojom::StreamFactory* factory);
private:
const base::UnguessableToken group_id_;
audio::mojom::LocalMuterAssociatedPtr muter_;
DISALLOW_COPY_AND_ASSIGN(AudioMutingSession);
};
} // namespace content
#endif // CONTENT_BROWSER_MEDIA_AUDIO_MUTING_SESSION_H_
......@@ -74,6 +74,7 @@ class MockAudioOutputStreamProviderClient
private:
mojo::Binding<media::mojom::AudioOutputStreamProviderClient> binding_;
DISALLOW_COPY_AND_ASSIGN(MockAudioOutputStreamProviderClient);
};
class MockStreamFactory : public audio::FakeStreamFactory {
......@@ -128,6 +129,7 @@ class MockStreamFactory : public audio::FakeStreamFactory {
}
StreamRequestData* stream_request_data_;
DISALLOW_COPY_AND_ASSIGN(MockStreamFactory);
};
// This struct collects test state we need without doing anything fancy.
......
......@@ -7,6 +7,7 @@
#include <utility>
#include "content/browser/media/audio_input_stream_broker.h"
#include "content/browser/media/audio_loopback_stream_broker.h"
#include "content/browser/media/audio_output_stream_broker.h"
namespace content {
......@@ -34,6 +35,22 @@ class AudioStreamBrokerFactoryImpl final : public AudioStreamBrokerFactory {
std::move(renderer_factory_client));
}
std::unique_ptr<AudioStreamBroker> CreateAudioLoopbackStreamBroker(
int render_process_id,
int render_frame_id,
std::unique_ptr<LoopbackSource> source,
const media::AudioParameters& params,
uint32_t shared_memory_count,
bool mute_source,
AudioStreamBroker::DeleterCallback deleter,
mojom::RendererAudioInputStreamFactoryClientPtr renderer_factory_client)
final {
return std::make_unique<AudioLoopbackStreamBroker>(
render_process_id, render_frame_id, std::move(source), params,
shared_memory_count, mute_source, std::move(deleter),
std::move(renderer_factory_client));
}
std::unique_ptr<AudioStreamBroker> CreateAudioOutputStreamBroker(
int render_process_id,
int render_frame_id,
......
......@@ -12,6 +12,7 @@
#include "base/macros.h"
#include "content/common/content_export.h"
#include "content/common/media/renderer_audio_input_stream_factory.mojom.h"
#include "content/public/browser/web_contents_observer.h"
#include "media/mojo/interfaces/audio_input_stream.mojom.h"
#include "media/mojo/interfaces/audio_output_stream.mojom.h"
......@@ -30,6 +31,7 @@ class AudioParameters;
}
namespace content {
class WebContents;
// An AudioStreamBroker is used to broker a connection between a client
// (typically renderer) and the audio service. It also sets up all objects
......@@ -57,6 +59,38 @@ class CONTENT_EXPORT AudioStreamBroker {
// Used for dependency injection into ForwardingAudioStreamFactory.
class CONTENT_EXPORT AudioStreamBrokerFactory {
public:
class CONTENT_EXPORT LoopbackSource : public WebContentsObserver {
public:
explicit LoopbackSource(WebContents* source_contents);
~LoopbackSource() override;
// Virtual for mocking in tests.
// Will return an empty token if the source is not present.
virtual base::UnguessableToken GetGroupID();
// Signals the source WebContents that capturing started.
virtual void OnStartCapturing();
// Signals the source WebContents that capturing stopped.
virtual void OnStopCapturing();
// Sets the closure to run when the source WebContents is gone.
void set_on_gone_closure(base::OnceClosure on_gone_closure) {
on_gone_closure_ = std::move(on_gone_closure);
}
// WebContentsObserver implementation.
void WebContentsDestroyed() override;
protected:
LoopbackSource();
private:
base::OnceClosure on_gone_closure_;
DISALLOW_COPY_AND_ASSIGN(LoopbackSource);
};
static std::unique_ptr<AudioStreamBrokerFactory> CreateImpl();
AudioStreamBrokerFactory();
......@@ -73,6 +107,17 @@ class CONTENT_EXPORT AudioStreamBrokerFactory {
mojom::RendererAudioInputStreamFactoryClientPtr
renderer_factory_client) = 0;
virtual std::unique_ptr<AudioStreamBroker> CreateAudioLoopbackStreamBroker(
int render_process_id,
int render_frame_id,
std::unique_ptr<LoopbackSource> source,
const media::AudioParameters& params,
uint32_t shared_memory_count,
bool mute_source,
AudioStreamBroker::DeleterCallback deleter,
mojom::RendererAudioInputStreamFactoryClientPtr
renderer_factory_client) = 0;
virtual std::unique_ptr<AudioStreamBroker> CreateAudioOutputStreamBroker(
int render_process_id,
int render_frame_id,
......
......@@ -79,6 +79,7 @@ void ForwardingAudioStreamFactory::CreateOutputStream(
const int process_id = frame->GetProcess()->GetID();
const int frame_id = frame->GetRoutingID();
outputs_
.insert(broker_factory_->CreateAudioOutputStreamBroker(
process_id, frame_id, ++stream_id_counter_, device_id, params,
......@@ -90,16 +91,53 @@ void ForwardingAudioStreamFactory::CreateOutputStream(
->CreateStream(GetFactory());
}
void ForwardingAudioStreamFactory::FrameDeleted(
RenderFrameHost* render_frame_host) {
void ForwardingAudioStreamFactory::CreateLoopbackStream(
RenderFrameHost* frame,
WebContents* source_contents,
const media::AudioParameters& params,
uint32_t shared_memory_count,
bool mute_source,
mojom::RendererAudioInputStreamFactoryClientPtr renderer_factory_client) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
CleanupStreamsBelongingTo(render_frame_host);
const int process_id = frame->GetProcess()->GetID();
const int frame_id = frame->GetRoutingID();
inputs_
.insert(broker_factory_->CreateAudioLoopbackStreamBroker(
process_id, frame_id,
std::make_unique<AudioStreamBrokerFactory::LoopbackSource>(
source_contents),
params, shared_memory_count, mute_source,
base::BindOnce(&ForwardingAudioStreamFactory::RemoveInput,
base::Unretained(this)),
std::move(renderer_factory_client)))
.first->get()
->CreateStream(GetFactory());
}
void ForwardingAudioStreamFactory::SetMuted(bool muted) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_NE(muted, IsMuted());
if (!muted) {
muter_.reset();
return;
}
muter_.emplace(group_id_);
if (remote_factory_)
muter_->Connect(remote_factory_.get());
}
void ForwardingAudioStreamFactory::WebContentsDestroyed() {
bool ForwardingAudioStreamFactory::IsMuted() const {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(outputs_.empty());
DCHECK(inputs_.empty());
return !!muter_;
}
void ForwardingAudioStreamFactory::FrameDeleted(
RenderFrameHost* render_frame_host) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
CleanupStreamsBelongingTo(render_frame_host);
}
void ForwardingAudioStreamFactory::CleanupStreamsBelongingTo(
......@@ -145,6 +183,10 @@ audio::mojom::StreamFactory* ForwardingAudioStreamFactory::GetFactory() {
remote_factory_.set_connection_error_handler(
base::BindOnce(&ForwardingAudioStreamFactory::ResetRemoteFactoryPtr,
base::Unretained(this)));
// Restore the muting session on reconnect.
if (muter_)
muter_->Connect(remote_factory_.get());
}
return remote_factory_.get();
......@@ -161,6 +203,8 @@ void ForwardingAudioStreamFactory::ResetRemoteFactoryPtr() {
remote_factory_.reset();
// The stream brokers will call a callback to be deleted soon, give them a
// chance to signal an error to the client first.
inputs_.clear();
outputs_.clear();
}
} // namespace content
......@@ -11,7 +11,9 @@
#include "base/containers/flat_set.h"
#include "base/containers/unique_ptr_adapters.h"
#include "base/macros.h"
#include "base/optional.h"
#include "base/unguessable_token.h"
#include "content/browser/media/audio_muting_session.h"
#include "content/browser/media/audio_stream_broker.h"
#include "content/common/content_export.h"
#include "content/common/media/renderer_audio_input_stream_factory.mojom.h"
......@@ -30,6 +32,7 @@ namespace content {
class AudioStreamBroker;
class RenderFrameHost;
class WebContents;
// This class handles stream creation operations for a WebContents.
// This class is operated on the UI thread.
......@@ -50,7 +53,6 @@ class CONTENT_EXPORT ForwardingAudioStreamFactory final
const base::UnguessableToken& group_id() { return group_id_; }
// TODO(https://crbug.com/803102): Add loopback and muting streams.
// TODO(https://crbug.com/787806): Automatically restore streams on audio
// service restart.
void CreateInputStream(
......@@ -67,10 +69,23 @@ class CONTENT_EXPORT ForwardingAudioStreamFactory final
const media::AudioParameters& params,
media::mojom::AudioOutputStreamProviderClientPtr client);
void CreateLoopbackStream(
RenderFrameHost* frame,
WebContents* source_contents,
const media::AudioParameters& params,
uint32_t shared_memory_count,
bool mute_source,
mojom::RendererAudioInputStreamFactoryClientPtr renderer_factory_client);
// Sets the muting state for all output streams created through this factory.
void SetMuted(bool muted);
// Returns the current muting state.
bool IsMuted() const;
// WebContentsObserver implementation. We observe these events so that we can
// clean up streams belonging to a frame when that frame is destroyed.
void FrameDeleted(RenderFrameHost* render_frame_host) final;
void WebContentsDestroyed() final;
// E.g. to override binder.
service_manager::Connector* get_connector_for_testing() {
......@@ -111,6 +126,9 @@ class CONTENT_EXPORT ForwardingAudioStreamFactory final
// remove it.
int stream_id_counter_ = 0;
// Instantiated when |outputs_| should be muted, empty otherwise.
base::Optional<AudioMutingSession> muter_;
StreamBrokerSet inputs_;
StreamBrokerSet outputs_;
......
......@@ -1363,6 +1363,7 @@ test("content_unittests") {
"../browser/manifest/manifest_icon_selector_unittest.cc",
"../browser/media/audible_metrics_unittest.cc",
"../browser/media/audio_input_stream_broker_unittest.cc",
"../browser/media/audio_loopback_stream_broker_unittest.cc",
"../browser/media/audio_output_stream_broker_unittest.cc",
"../browser/media/audio_stream_monitor_unittest.cc",
"../browser/media/capture/audio_mirroring_manager_unittest.cc",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment