blob: ff12ffd69af1b2900ca3509a83549f7b445ef4f0 [file] [log] [blame]
// 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/forwarding_audio_stream_factory.h"
#include <utility>
#include "base/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "base/task/post_task.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/web_contents/web_contents_impl.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 "media/base/user_input_monitor.h"
#include "services/audio/public/mojom/constants.mojom.h"
#include "services/service_manager/public/cpp/connector.h"
#include "ui/gfx/geometry/size.h"
namespace content {
ForwardingAudioStreamFactory::Core::Core(
base::WeakPtr<ForwardingAudioStreamFactory> owner,
media::UserInputMonitorBase* user_input_monitor,
std::unique_ptr<service_manager::Connector> connector,
std::unique_ptr<AudioStreamBrokerFactory> broker_factory)
: user_input_monitor_(user_input_monitor),
owner_(std::move(owner)),
broker_factory_(std::move(broker_factory)),
group_id_(base::UnguessableToken::Create()),
connector_(std::move(connector)),
weak_ptr_factory_(this) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(owner_);
DCHECK(broker_factory_);
DCHECK(connector_);
}
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,
audio::mojom::AudioProcessingConfigPtr processing_config,
mojom::RendererAudioInputStreamFactoryClientPtr 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,
shared_memory_count, user_input_monitor_, 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 std::string& device_id,
const media::AudioParameters& params,
const base::Optional<base::UnguessableToken>& processing_id,
media::mojom::AudioOutputStreamProviderClientPtr client) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// |this| owns |outputs_|, so Unretained is safe.
outputs_
.insert(broker_factory_->CreateAudioOutputStreamBroker(
render_process_id, render_frame_id, ++stream_id_counter_, device_id,
params, group_id_, processing_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,
mojom::RendererAudioInputStreamFactoryClientPtr 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);
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::UI},
base::BindOnce(&ForwardingAudioStreamFactory::LoopbackStreamStarted,
owner_));
}
void ForwardingAudioStreamFactory::Core::RemoveLoopbackSink(
AudioStreamBroker::LoopbackSink* sink) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
loopback_sinks_.erase(sink);
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::UI},
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);
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,
media::UserInputMonitorBase* user_input_monitor,
std::unique_ptr<service_manager::Connector> connector,
std::unique_ptr<AudioStreamBrokerFactory> broker_factory)
: WebContentsObserver(web_contents), core_(), weak_ptr_factory_(this) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
core_ =
std::make_unique<Core>(weak_ptr_factory_.GetWeakPtr(), user_input_monitor,
std::move(connector), 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.
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce([](std::unique_ptr<Core>) {}, std::move(core_)));
}
void ForwardingAudioStreamFactory::LoopbackStreamStarted() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
web_contents()->IncrementCapturerCount(gfx::Size());
}
void ForwardingAudioStreamFactory::LoopbackStreamStopped() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
web_contents()->DecrementCapturerCount();
}
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.
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(&Core::SetMuted, base::Unretained(core_.get()), muted));
}
}
bool ForwardingAudioStreamFactory::IsMuted() const {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
return is_muted_;
}
void ForwardingAudioStreamFactory::FrameDeleted(
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.
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(&Core::CleanupStreamsBelongingTo,
base::Unretained(core_.get()),
render_frame_host->GetProcess()->GetID(),
render_frame_host->GetRoutingID()));
}
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();
}
audio::mojom::StreamFactory* 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());
connector_->BindInterface(audio::mojom::kServiceName,
mojo::MakeRequest(&remote_factory_));
// Unretained is safe because |this| owns |remote_factory_|.
remote_factory_.set_connection_error_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