#include "services/audio/loopback_signal_provider.h"
#include <memory>
#include <utility>
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/trace_event/trace_event.h"
#include "base/types/zip.h"
#include "media/base/audio_bus.h"
#include "media/base/vector_math.h"
namespace audio {
namespace {
constexpr base::TimeDelta kInitialCaptureDelay = base::Milliseconds(20);
}
LoopbackSignalProvider::LoopbackSignalProvider(
const media::AudioParameters& output_params,
std::unique_ptr<LoopbackGroupObserver> loopback_group_observer)
: output_params_(output_params),
loopback_group_observer_(std::move(loopback_group_observer)),
capture_delay_(kInitialCaptureDelay),
transfer_bus_(media::AudioBus::Create(output_params)) {}
LoopbackSignalProvider::~LoopbackSignalProvider() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
TRACE_EVENT0("audio", "LoopbackSignalProvider::~LoopbackSignalProvider");
loopback_group_observer_->StopObserving();
while (!snoopers_.empty()) {
OnSourceRemoved(snoopers_.begin()->first);
}
}
void LoopbackSignalProvider::Start() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
TRACE_EVENT0("audio", "LoopbackSignalProvider::Start");
loopback_group_observer_->ForEachSource(base::BindRepeating(
&LoopbackSignalProvider::OnSourceAdded, base::Unretained(this)));
loopback_group_observer_->StartObserving(this);
}
base::TimeTicks LoopbackSignalProvider::PullLoopbackData(
media::AudioBus* destination,
base::TimeTicks capture_time,
double volume) {
TRACE_EVENT2(
"audio", "LoopbackSignalProvider::PullLoopbackData", "capture time (µs)",
(capture_time - base::TimeTicks()).InMicroseconds(), "volume", volume);
base::AutoLock scoped_lock(lock_);
base::TimeTicks delayed_capture_time = capture_time - capture_delay_;
for (auto& map_entry : snoopers_) {
const std::optional<base::TimeTicks> suggestion =
map_entry.second->SuggestLatestRenderTime(destination->frames());
if (suggestion.value_or(delayed_capture_time) < delayed_capture_time) {
const base::TimeDelta increase = delayed_capture_time - (*suggestion);
TRACE_EVENT_INSTANT2("audio", "PullLoopbackData Capture Delay Change",
TRACE_EVENT_SCOPE_THREAD, "old capture delay (µs)",
capture_delay_.InMicroseconds(), "change (µs)",
increase.InMicroseconds());
delayed_capture_time = *suggestion;
capture_delay_ += increase;
}
}
TRACE_COUNTER_ID1("audio", "Loopback Capture Delay (µs)", this,
capture_delay_.InMicroseconds());
auto it = snoopers_.begin();
if (it == snoopers_.end()) {
destination->Zero();
} else {
it->second->Render(delayed_capture_time, destination);
destination->Scale(volume);
++it;
if (it != snoopers_.end()) {
do {
it->second->Render(delayed_capture_time, transfer_bus_.get());
for (auto [src_ch, dest_ch] : base::zip(transfer_bus_->AllChannels(),
destination->AllChannels())) {
media::vector_math::FMAC(src_ch, volume, dest_ch);
}
++it;
} while (it != snoopers_.end());
}
}
return delayed_capture_time;
}
void LoopbackSignalProvider::OnSourceAdded(LoopbackSource* source) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!base::TimeTicks::IsHighResolution()) {
TRACE_EVENT_INSTANT0("audio",
"LoopbackSignalProvider::AddLoopbackSource Rejected",
TRACE_EVENT_SCOPE_THREAD);
return;
}
TRACE_EVENT1("audio", "LoopbackSignalProvider::AddLoopbackSource", "source",
static_cast<void*>(source));
std::unique_ptr<SnooperNode> snooper_node = std::make_unique<SnooperNode>(
source->GetAudioParameters(), output_params_);
base::AutoLock scoped_lock(lock_);
const auto emplace_result =
snoopers_.emplace(source, std::move(snooper_node));
DCHECK(emplace_result.second);
source->StartSnooping(emplace_result.first->second.get());
}
void LoopbackSignalProvider::OnSourceRemoved(LoopbackSource* source) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
TRACE_EVENT1("audio", "LoopbackSignalProvider::RemoveLoopbackSource",
"source", static_cast<void*>(source));
std::unique_ptr<SnooperNode> snooper_to_delete;
{
base::AutoLock scoped_lock(lock_);
const auto snoop_it = snoopers_.find(source);
if (snoop_it == snoopers_.end()) {
return;
}
source->StopSnooping(snoop_it->second.get());
snooper_to_delete = std::move(snoop_it->second);
snoopers_.erase(snoop_it);
}
}
}