blob: 57c15f52216ff7eceb11e4378c03b0292455683c [file]
// 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 "services/audio/loopback_stream.h"
#include <algorithm>
#include <string>
#include <utility>
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/sync_socket.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/default_tick_clock.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"
#include "mojo/public/cpp/system/buffer.h"
#include "mojo/public/cpp/system/platform_handle.h"
#include "third_party/perfetto/include/perfetto/tracing/track_event_args.h"
namespace audio {
// static
constexpr double LoopbackStream::kMaxVolume;
LoopbackStream::LoopbackStream(
CreatedCallback created_callback,
BindingLostCallback binding_lost_callback,
scoped_refptr<base::SequencedTaskRunner> loopback_task_runner,
mojo::PendingReceiver<media::mojom::AudioInputStream> receiver,
mojo::PendingRemote<media::mojom::AudioInputStreamClient> client,
mojo::PendingRemote<media::mojom::AudioInputStreamObserver> observer,
const media::AudioParameters& params,
uint32_t shared_memory_count,
LoopbackCoordinator* coordinator,
const base::UnguessableToken& group_id)
: binding_lost_callback_(std::move(binding_lost_callback)),
receiver_(this, std::move(receiver)),
client_(std::move(client)),
observer_(std::move(observer)),
loopback_signal_provider_(
params,
LoopbackGroupObserver::CreateMatchingGroupObserver(coordinator,
group_id)),
loopback_signal_forwarder_(
nullptr,
base::OnTaskRunnerDeleter(loopback_task_runner)) {
TRACE_EVENT1("audio", "LoopbackStream::LoopbackStream", "params",
params.AsHumanReadableString());
// Generate an error and shut down automatically whenever any of the mojo
// bindings is closed.
receiver_.set_disconnect_handler(
base::BindOnce(&LoopbackStream::OnError, base::Unretained(this)));
client_.set_disconnect_handler(
base::BindOnce(&LoopbackStream::OnError, base::Unretained(this)));
observer_.set_disconnect_handler(
base::BindOnce(&LoopbackStream::OnError, base::Unretained(this)));
// Construct the components of the AudioDataPipe, for delivering the data to
// the consumer. If successful, create the LoopbackSignalForwarder too.
base::CancelableSyncSocket foreign_socket;
std::unique_ptr<InputSyncWriter> writer = InputSyncWriter::Create(
base::BindRepeating(
[](const std::string& message) { VLOG(1) << message; }),
shared_memory_count, params, &foreign_socket);
if (writer) {
base::UnsafeSharedMemoryRegion shared_memory_region =
writer->TakeSharedMemoryRegion();
mojo::PlatformHandle socket_handle;
if (shared_memory_region.IsValid()) {
socket_handle = mojo::PlatformHandle(foreign_socket.Take());
if (socket_handle.is_valid()) {
std::move(created_callback)
.Run({std::in_place, std::move(shared_memory_region),
std::move(socket_handle)});
loopback_signal_forwarder_.reset(new LoopbackSignalForwarder(
std::move(loopback_task_runner), params, std::move(writer),
&loopback_signal_provider_));
return; // Success!
}
}
}
// If this point is reached, one or more AudioDataPipe components failed to
// initialize. Report the error.
std::move(created_callback).Run(nullptr);
OnError();
}
LoopbackStream::~LoopbackStream() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
TRACE_EVENT0("audio", "LoopbackStream::~LoopbackStream");
if (loopback_signal_forwarder_) {
// Since the LoopbackSignalProvider is about to be destroyed, we need to
// prevent the LoopbackSignalForwarder from using it afterwards.
loopback_signal_forwarder_->InvalidateLoopbackSignalProvider();
}
}
void LoopbackStream::Record() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!loopback_signal_forwarder_ || loopback_signal_forwarder_->is_started()) {
return;
}
TRACE_EVENT0("audio", "LoopbackStream::Record");
// Start the data flow.
loopback_signal_forwarder_->Start();
if (observer_) {
observer_->DidStartRecording();
}
}
void LoopbackStream::SetVolume(double volume) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
TRACE_EVENT_INSTANT("audio", "LoopbackStream::SetVolume", "volume", volume);
if (!std::isfinite(volume) || volume < 0.0) {
receiver_.ReportBadMessage("Invalid volume");
OnError();
return;
}
if (loopback_signal_forwarder_) {
loopback_signal_forwarder_->SetVolume(std::min(volume, kMaxVolume));
}
}
void LoopbackStream::OnError() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!binding_lost_callback_) {
return; // OnError() was already called.
}
TRACE_EVENT0("audio", "LoopbackStream::OnError");
receiver_.reset();
if (client_) {
client_->OnError(media::mojom::InputStreamErrorCode::kUnknown);
client_.reset();
}
observer_.reset();
// Post a task to run the BindingLostCallback, since this method can be called
// from the constructor.
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
[](base::WeakPtr<LoopbackStream> weak_self,
BindingLostCallback callback) {
if (auto* self = weak_self.get()) {
std::move(callback).Run(self);
}
},
weak_factory_.GetWeakPtr(), std::move(binding_lost_callback_)));
// No need to shut down anything else, as the destructor will run soon and
// take care of the rest.
}
LoopbackStream::LoopbackSignalForwarder::LoopbackSignalForwarder(
scoped_refptr<base::SequencedTaskRunner> loopback_task_runner,
const media::AudioParameters& output_params,
std::unique_ptr<InputSyncWriter> writer,
LoopbackSignalProvider* loopback_signal_provider)
: clock_(base::DefaultTickClock::GetInstance()),
loopback_task_runner_(loopback_task_runner),
output_params_(output_params),
writer_(std::move(writer)),
mix_bus_(media::AudioBus::Create(output_params_)),
loopback_signal_provider_(loopback_signal_provider) {}
void LoopbackStream::LoopbackSignalForwarder::SetVolume(double volume) {
DCHECK_CALLED_ON_VALID_SEQUENCE(control_sequence_);
base::AutoLock scoped_lock(lock_);
volume_ = volume;
}
void LoopbackStream::LoopbackSignalForwarder::Start() {
DCHECK_CALLED_ON_VALID_SEQUENCE(control_sequence_);
DCHECK(!is_started());
{
// Even though there is no risk of simultaneous access yet we need to take
// the lock to satisfy the GUARDED_BY annotation.
base::AutoLock scoped_lock(lock_);
if (!loopback_signal_provider_) {
return;
}
loopback_signal_provider_->Start();
}
timer_.emplace();
// Note: GenerateMoreAudio() will schedule the timer.
first_generate_time_ = clock_->NowTicks();
frames_elapsed_ = 0;
next_generate_time_ = first_generate_time_;
loopback_task_runner_->PostTask(
FROM_HERE,
// Unretained is safe because the destructor will always be invoked from a
// task that runs afterwards.
base::BindOnce(&LoopbackSignalForwarder::GenerateMoreAudio,
base::Unretained(this)));
}
void LoopbackStream::LoopbackSignalForwarder::
InvalidateLoopbackSignalProvider() {
DCHECK_CALLED_ON_VALID_SEQUENCE(control_sequence_);
base::AutoLock scoped_lock(lock_);
loopback_signal_provider_ = nullptr;
}
LoopbackStream::LoopbackSignalForwarder::~LoopbackSignalForwarder() {
DCHECK(loopback_task_runner_->RunsTasksInCurrentSequence());
DCHECK_EQ(loopback_signal_provider_, nullptr);
}
void LoopbackStream::LoopbackSignalForwarder::GenerateMoreAudio() {
DCHECK(loopback_task_runner_->RunsTasksInCurrentSequence());
TRACE_EVENT("audio", "GenerateMoreAudio", perfetto::Flow::FromPointer(this));
double output_volume;
base::TimeTicks delayed_capture_time;
{
base::AutoLock scoped_lock(lock_);
if (!loopback_signal_provider_) {
// Access to the provider has been removed because the loopback stream
// is shutting down. The LoopbackSignalForwarder will be destroyed soon.
return;
}
output_volume = volume_;
delayed_capture_time = loopback_signal_provider_->PullLoopbackData(
mix_bus_.get(), next_generate_time_, volume_);
}
// Insert the result into the AudioDataPipe.
writer_->Write(mix_bus_.get(), output_volume, delayed_capture_time, {});
// Determine when to generate more audio again. This is done by advancing
// the frame count by one interval's worth, then computing the TimeTicks
// corresponding to the new frame count. Also, check the clock to detect
// when the user's machine is overloaded and the output needs to skip
// forward one or more intervals.
const int frames_per_buffer = mix_bus_->frames();
frames_elapsed_ += frames_per_buffer;
next_generate_time_ =
first_generate_time_ +
base::Microseconds(frames_elapsed_ * base::Time::kMicrosecondsPerSecond /
output_params_.sample_rate());
const base::TimeTicks now = clock_->NowTicks();
if (next_generate_time_ < now) {
TRACE_EVENT_INSTANT("audio", "GenerateMoreAudio Is Behind", "µsec_behind",
(now - next_generate_time_).InMicroseconds());
// Audio generation has fallen behind. Skip-ahead the frame counter so
// that audio generation will resume for the next buffer after the one
// that should be generating right now. http://crbug.com/847487
const int64_t target_frame_count =
(now - first_generate_time_).InMicroseconds() *
output_params_.sample_rate() / base::Time::kMicrosecondsPerSecond;
frames_elapsed_ =
(target_frame_count / frames_per_buffer + 1) * frames_per_buffer;
next_generate_time_ =
first_generate_time_ +
base::Microseconds(frames_elapsed_ *
base::Time::kMicrosecondsPerSecond /
output_params_.sample_rate());
}
// Note: It's acceptable for `next_generate_time_` to be slightly before
// `now` due to integer truncation behaviors in the math above. The timer
// task started below will just run immediately and there will be no harmful
// effects in the next GenerateMoreAudio() call. http://crbug.com/847487
timer_->Start(FROM_HERE, next_generate_time_, this,
&LoopbackSignalForwarder::GenerateMoreAudio,
base::subtle::DelayPolicy::kPrecise);
}
} // namespace audio