blob: 2d9127f3564de29be2d1e434a2e56c2501c2f9a8 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/media/forwarding_audio_stream_factory.h"
#include <utility>
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/no_destructor.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/guest_page_holder_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/audio_service.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "media/base/audio_parameters.h"
#include "ui/gfx/geometry/size.h"
namespace content {
namespace {
ForwardingAudioStreamFactory::AudioStreamFactoryBinder&
GetAudioStreamFactoryBinderOverride() {
static base::NoDestructor<
ForwardingAudioStreamFactory::AudioStreamFactoryBinder>
binder;
return *binder;
}
void BindStreamFactoryFromUIThread(
mojo::PendingReceiver<media::mojom::AudioStreamFactory> receiver) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
const auto& binder_override = GetAudioStreamFactoryBinderOverride();
if (binder_override) {
binder_override.Run(std::move(receiver));
return;
}
GetAudioService().BindStreamFactory(std::move(receiver));
}
} // namespace
ForwardingAudioStreamFactory::Core::Core(
base::WeakPtr<ForwardingAudioStreamFactory> owner,
std::unique_ptr<AudioStreamBrokerFactory> broker_factory)
: owner_(std::move(owner)),
broker_factory_(std::move(broker_factory)),
group_id_(base::UnguessableToken::Create()) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(owner_);
DCHECK(broker_factory_);
}
ForwardingAudioStreamFactory::Core::~Core() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
for (AudioStreamBroker::LoopbackSink* sink : loopback_sinks_)
sink->OnSourceGone();
}
base::WeakPtr<ForwardingAudioStreamFactory::Core>
ForwardingAudioStreamFactory::Core::AsWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
void ForwardingAudioStreamFactory::Core::CreateInputStream(
int render_process_id,
int render_frame_id,
const std::string& device_id,
const media::AudioParameters& params,
uint32_t shared_memory_count,
bool enable_agc,
media::mojom::AudioProcessingConfigPtr processing_config,
mojo::PendingRemote<blink::mojom::RendererAudioInputStreamFactoryClient>
renderer_factory_client) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// |this| owns |inputs_|, so Unretained is safe.
inputs_
.insert(broker_factory_->CreateAudioInputStreamBroker(
render_process_id, render_frame_id, device_id, params, group_id_,
shared_memory_count, enable_agc, std::move(processing_config),
base::BindOnce(&ForwardingAudioStreamFactory::Core::RemoveInput,
base::Unretained(this)),
std::move(renderer_factory_client)))
.first->get()
->CreateStream(GetFactory());
}
void ForwardingAudioStreamFactory::Core::AssociateInputAndOutputForAec(
const base::UnguessableToken& input_stream_id,
const std::string& raw_output_device_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// Avoid spawning a factory if this for some reason gets called with an
// invalid |input_stream_id| before any streams are created.
if (!inputs_.empty()) {
GetFactory()->AssociateInputAndOutputForAec(input_stream_id,
raw_output_device_id);
}
}
void ForwardingAudioStreamFactory::Core::CreateOutputStream(
int render_process_id,
int render_frame_id,
const GlobalRenderFrameHostToken& main_frame_token,
const std::string& device_id,
const media::AudioParameters& params,
mojo::PendingRemote<media::mojom::AudioOutputStreamProviderClient> client) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// |this| owns |outputs_|, so Unretained is safe.
outputs_
.insert(broker_factory_->CreateAudioOutputStreamBroker(
render_process_id, render_frame_id, main_frame_token,
++stream_id_counter_, device_id, params, group_id_,
base::BindOnce(&ForwardingAudioStreamFactory::Core::RemoveOutput,
base::Unretained(this)),
std::move(client)))
.first->get()
->CreateStream(GetFactory());
}
void ForwardingAudioStreamFactory::Core::CreateLoopbackStream(
int render_process_id,
int render_frame_id,
AudioStreamBroker::LoopbackSource* loopback_source,
const media::AudioParameters& params,
uint32_t shared_memory_count,
bool mute_source,
mojo::PendingRemote<blink::mojom::RendererAudioInputStreamFactoryClient>
renderer_factory_client) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(loopback_source);
TRACE_EVENT_BEGIN1("audio", "CreateLoopbackStream", "group",
group_id_.GetLowForSerialization());
// |this| owns |inputs_|, so Unretained is safe.
inputs_
.insert(broker_factory_->CreateAudioLoopbackStreamBroker(
render_process_id, render_frame_id, loopback_source, params,
shared_memory_count, mute_source,
base::BindOnce(&ForwardingAudioStreamFactory::Core::RemoveInput,
base::Unretained(this)),
std::move(renderer_factory_client)))
.first->get()
->CreateStream(GetFactory());
TRACE_EVENT_END1("audio", "CreateLoopbackStream", "source",
loopback_source->GetGroupID().GetLowForSerialization());
}
void ForwardingAudioStreamFactory::Core::SetMuted(bool muted) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK_NE(muted, !!muter_);
TRACE_EVENT_INSTANT2("audio", "SetMuted", TRACE_EVENT_SCOPE_THREAD, "group",
group_id_.GetLowForSerialization(), "muted", muted);
if (!muted) {
muter_.reset();
return;
}
muter_.emplace(group_id_);
if (remote_factory_)
muter_->Connect(remote_factory_.get());
}
void ForwardingAudioStreamFactory::Core::AddLoopbackSink(
AudioStreamBroker::LoopbackSink* sink) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
loopback_sinks_.insert(sink);
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&ForwardingAudioStreamFactory::LoopbackStreamStarted,
owner_));
}
void ForwardingAudioStreamFactory::Core::RemoveLoopbackSink(
AudioStreamBroker::LoopbackSink* sink) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
loopback_sinks_.erase(sink);
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&ForwardingAudioStreamFactory::LoopbackStreamStopped,
owner_));
}
const base::UnguessableToken& ForwardingAudioStreamFactory::Core::GetGroupID() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
return group_id();
}
// static
ForwardingAudioStreamFactory* ForwardingAudioStreamFactory::ForFrame(
RenderFrameHost* frame) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!frame) {
return nullptr;
}
GuestPageHolderImpl* guest_holder = GuestPageHolderImpl::FromRenderFrameHost(
static_cast<RenderFrameHostImpl&>(*frame));
if (guest_holder) {
return guest_holder->GetAudioStreamFactory();
}
auto* contents =
static_cast<WebContentsImpl*>(WebContents::FromRenderFrameHost(frame));
if (!contents) {
return nullptr;
}
return contents->GetAudioStreamFactory();
}
// static
ForwardingAudioStreamFactory::Core* ForwardingAudioStreamFactory::CoreForFrame(
RenderFrameHost* frame) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
ForwardingAudioStreamFactory* forwarding_factory =
ForwardingAudioStreamFactory::ForFrame(frame);
return forwarding_factory ? forwarding_factory->core() : nullptr;
}
ForwardingAudioStreamFactory::ForwardingAudioStreamFactory(
WebContents* web_contents,
std::unique_ptr<AudioStreamBrokerFactory> broker_factory)
: WebContentsObserver(web_contents), core_() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
core_ = std::make_unique<Core>(weak_ptr_factory_.GetWeakPtr(),
std::move(broker_factory));
}
ForwardingAudioStreamFactory::~ForwardingAudioStreamFactory() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Ensure |core_| is deleted on the right thread. DeleteOnIOThread isn't used
// as it doesn't post in case it is already executed on the right thread. That
// causes issues in unit tests where the UI thread and the IO thread are the
// same.
GetIOThreadTaskRunner({})->PostTask(
FROM_HERE, base::DoNothingWithBoundArgs(std::move(core_)));
}
void ForwardingAudioStreamFactory::LoopbackStreamStarted() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
capture_handle_ = web_contents()->IncrementCapturerCount(
gfx::Size(), /*stay_hidden=*/false,
/*stay_awake=*/true, /*is_activity=*/true);
}
void ForwardingAudioStreamFactory::LoopbackStreamStopped() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
capture_handle_.RunAndReset();
}
void ForwardingAudioStreamFactory::SetMuted(bool muted) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (is_muted_ != muted) {
is_muted_ = muted;
// Unretained is safe since the destruction of |core_| will be posted to the
// IO thread later.
GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&Core::SetMuted, base::Unretained(core_.get()), muted));
}
}
bool ForwardingAudioStreamFactory::IsMuted() const {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
return is_muted_;
}
void ForwardingAudioStreamFactory::RenderFrameDeleted(
RenderFrameHost* render_frame_host) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(render_frame_host);
// Unretained is safe since the destruction of |core_| will be posted to the
// IO thread later.
GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&Core::CleanupStreamsBelongingTo,
base::Unretained(core_.get()),
render_frame_host->GetProcess()->GetDeprecatedID(),
render_frame_host->GetRoutingID()));
}
void ForwardingAudioStreamFactory::OverrideAudioStreamFactoryBinderForTesting(
AudioStreamFactoryBinder binder) {
GetAudioStreamFactoryBinderOverride() = std::move(binder);
}
void ForwardingAudioStreamFactory::Core::CleanupStreamsBelongingTo(
int render_process_id,
int render_frame_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
TRACE_EVENT_BEGIN2("audio", "CleanupStreamsBelongingTo", "group",
group_id_.GetLowForSerialization(), "process id",
render_process_id);
auto match_rfh =
[render_process_id, render_frame_id](
const std::unique_ptr<AudioStreamBroker>& broker) -> bool {
return broker->render_process_id() == render_process_id &&
broker->render_frame_id() == render_frame_id;
};
base::EraseIf(outputs_, match_rfh);
base::EraseIf(inputs_, match_rfh);
ResetRemoteFactoryPtrIfIdle();
TRACE_EVENT_END1("audio", "CleanupStreamsBelongingTo", "frame_id",
render_frame_id);
}
void ForwardingAudioStreamFactory::Core::RemoveInput(
AudioStreamBroker* broker) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
size_t removed = inputs_.erase(broker);
DCHECK_EQ(1u, removed);
ResetRemoteFactoryPtrIfIdle();
}
void ForwardingAudioStreamFactory::Core::RemoveOutput(
AudioStreamBroker* broker) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
size_t removed = outputs_.erase(broker);
DCHECK_EQ(1u, removed);
ResetRemoteFactoryPtrIfIdle();
}
media::mojom::AudioStreamFactory*
ForwardingAudioStreamFactory::Core::GetFactory() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!remote_factory_) {
TRACE_EVENT_INSTANT1(
"audio", "ForwardingAudioStreamFactory: Binding new factory",
TRACE_EVENT_SCOPE_THREAD, "group", group_id_.GetLowForSerialization());
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&BindStreamFactoryFromUIThread,
remote_factory_.BindNewPipeAndPassReceiver()));
// Unretained is safe because |this| owns |remote_factory_|.
remote_factory_.set_disconnect_handler(base::BindOnce(
&ForwardingAudioStreamFactory::Core::ResetRemoteFactoryPtr,
base::Unretained(this)));
// Restore the muting session on reconnect.
if (muter_)
muter_->Connect(remote_factory_.get());
}
return remote_factory_.get();
}
void ForwardingAudioStreamFactory::Core::ResetRemoteFactoryPtrIfIdle() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (inputs_.empty() && outputs_.empty())
ResetRemoteFactoryPtr();
}
void ForwardingAudioStreamFactory::Core::ResetRemoteFactoryPtr() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (remote_factory_) {
TRACE_EVENT_INSTANT1(
"audio", "ForwardingAudioStreamFactory: Resetting factory",
TRACE_EVENT_SCOPE_THREAD, "group", group_id_.GetLowForSerialization());
}
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.
}
} // namespace content