#include "services/audio/output_stream.h"
#include <inttypes.h>
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/hash/hash.h"
#include "base/strings/stringprintf.h"
#include "base/task/sequenced_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "third_party/perfetto/include/perfetto/tracing/track.h"
namespace audio {
std::string GetCtorLogString(media::AudioManager* audio_manager,
const std::string& device_id,
const media::AudioParameters& params) {
return base::StringPrintf(
"Ctor({audio_manager_name=%s}, {device_id=%s}, {params=[%s]})",
audio_manager->GetName(), device_id.c_str(),
params.AsHumanReadableString().c_str());
}
class AudibilityHelperImpl : public OutputStream::AudibilityHelper {
public:
static constexpr float kSilenceThresholdDBFS = -72.24719896f;
static constexpr base::TimeDelta kGlitchTolerance = base::Milliseconds(100);
static constexpr base::TimeDelta kPollingPeriod = base::Seconds(1) / 15;
static constexpr base::TimeTicks kForcedSilenceStartTime =
base::TimeTicks::Min();
AudibilityHelperImpl() = default;
~AudibilityHelperImpl() override = default;
void StartPolling(
GetPowerLevelCB get_power_level_cb,
OnAudibleStateChangedCB on_audible_state_changed_cb) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
CHECK(!get_power_level_cb_ && get_power_level_cb);
CHECK(!on_audible_state_changed_cb_ && on_audible_state_changed_cb);
get_power_level_cb_ = std::move(get_power_level_cb);
on_audible_state_changed_cb_ = std::move(on_audible_state_changed_cb);
CHECK(!poll_timer_.IsRunning());
CHECK(!is_audible_);
first_time_silent_ = kForcedSilenceStartTime;
poll_timer_.Start(FROM_HERE, kPollingPeriod,
base::BindRepeating(&AudibilityHelperImpl::PollAudioLevel,
base::Unretained(this)));
}
void StopPolling() override {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
poll_timer_.Stop();
if (is_audible_) {
is_audible_ = false;
on_audible_state_changed_cb_.Run(false);
}
get_power_level_cb_.Reset();
on_audible_state_changed_cb_.Reset();
}
bool IsAudible() override {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
return is_audible_;
}
private:
void PollAudioLevel() {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
bool was_audible = is_audible_;
is_audible_ = ComputeIsAudible();
if (is_audible_ != was_audible) {
on_audible_state_changed_cb_.Run(is_audible_);
}
}
bool ComputeIsAudible() {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
const float power_dbfs = get_power_level_cb_.Run();
const bool currently_audible = power_dbfs >= kSilenceThresholdDBFS;
const base::TimeTicks now = base::TimeTicks::Now();
if (currently_audible) {
first_time_silent_ = std::nullopt;
return true;
}
if (!first_time_silent_) {
first_time_silent_ = now;
}
const base::TimeDelta silence_duration = now - first_time_silent_.value();
return silence_duration <= kGlitchTolerance;
}
SEQUENCE_CHECKER(owning_sequence_);
GetPowerLevelCB get_power_level_cb_;
OnAudibleStateChangedCB on_audible_state_changed_cb_;
base::RepeatingTimer poll_timer_;
std::optional<base::TimeTicks> first_time_silent_;
bool is_audible_ = false;
};
OutputStream::OutputStream(
CreatedCallback created_callback,
DeleteCallback delete_callback,
ManagedDeviceOutputStreamCreateCallback
managed_device_output_stream_create_callback,
mojo::PendingReceiver<media::mojom::AudioOutputStream> stream_receiver,
mojo::PendingReceiver<media::mojom::DeviceSwitchInterface>
device_switch_receiver,
mojo::PendingAssociatedRemote<media::mojom::AudioOutputStreamObserver>
observer,
mojo::PendingRemote<media::mojom::AudioLog> log,
media::AudioManager* audio_manager,
const std::string& output_device_id,
const media::AudioParameters& params,
LoopbackCoordinator* coordinator,
const base::UnguessableToken& loopback_group_id)
: delete_callback_(std::move(delete_callback)),
receiver_(this, std::move(stream_receiver)),
device_switch_receiver_(this, std::move(device_switch_receiver)),
observer_(std::move(observer)),
log_(std::move(log)),
coordinator_(coordinator),
reader_(log_ ? base::BindRepeating(&media::mojom::AudioLog::OnLogMessage,
base::Unretained(log_.get()))
: base::DoNothing(),
params,
&foreign_socket_),
controller_(audio_manager,
this,
params,
output_device_id,
&reader_,
std::move(managed_device_output_stream_create_callback)),
loopback_group_id_(loopback_group_id),
audibility_helper_(std::make_unique<AudibilityHelperImpl>()) {
DCHECK(receiver_.is_bound());
DCHECK(created_callback);
DCHECK(delete_callback_);
DCHECK(coordinator_);
TRACE_EVENT_BEGIN("audio", "audio::OutputStream",
perfetto::Track::FromPointer(this));
TRACE_EVENT_BEGIN("audio", "OutputStream", perfetto::Track::FromPointer(this),
"device id", output_device_id, "params",
params.AsHumanReadableString());
SendLogMessage(
"%s", GetCtorLogString(audio_manager, output_device_id, params).c_str());
base::RepeatingClosure error_handler =
base::BindRepeating(&OutputStream::OnError, base::Unretained(this));
receiver_.set_disconnect_handler(error_handler);
if (device_switch_receiver_.is_bound()) {
device_switch_receiver_.set_disconnect_handler(error_handler);
}
if (observer_)
observer_.set_disconnect_handler(std::move(error_handler));
if (log_)
log_->OnCreated(params, output_device_id);
coordinator_->AddMember(loopback_group_id_, &controller_);
if (!reader_.IsValid() || !controller_.CreateStream()) {
std::move(created_callback).Run(nullptr);
return;
}
CreateAudioPipe(std::move(created_callback));
}
OutputStream::~OutputStream() {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
if (log_) {
log_->OnClosed();
}
if (observer_) {
observer_.ResetWithReason(
static_cast<uint32_t>(media::mojom::AudioOutputStreamObserver::
DisconnectReason::kTerminatedByClient),
std::string());
}
controller_.Close();
coordinator_->RemoveMember(&controller_);
if (audibility_helper_->IsAudible()) {
TRACE_EVENT_END("audio", perfetto::Track::FromPointer(this));
}
if (playing_) {
TRACE_EVENT_END("audio", perfetto::Track::FromPointer(this));
}
TRACE_EVENT_END("audio",
perfetto::Track::FromPointer(this));
TRACE_EVENT_END("audio",
perfetto::Track::FromPointer(this));
}
void OutputStream::Play() {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
SendLogMessage("%s()", __func__);
controller_.Play();
if (log_)
log_->OnStarted();
}
void OutputStream::Pause() {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
SendLogMessage("%s()", __func__);
controller_.Pause();
if (log_)
log_->OnStopped();
}
void OutputStream::Flush() {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
SendLogMessage("%s()", __func__);
controller_.Flush();
}
void OutputStream::SetVolume(double volume) {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
TRACE_EVENT_INSTANT("audio", "SetVolume", perfetto::Track::FromPointer(this),
"volume", volume);
if (volume < 0 || volume > 1) {
receiver_.ReportBadMessage("Invalid volume");
OnControllerError();
return;
}
controller_.SetVolume(volume);
if (log_)
log_->OnSetVolume(volume);
}
void OutputStream::SwitchAudioOutputDeviceId(
const std::string& output_device_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
TRACE_EVENT_INSTANT("audio", "SwitchAudioOutputDeviceId",
perfetto::Track::FromPointer(this), "device_id",
output_device_id);
controller_.SwitchAudioOutputDeviceId(output_device_id);
}
void OutputStream::CreateAudioPipe(CreatedCallback created_callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
DCHECK(reader_.IsValid());
TRACE_EVENT_INSTANT("audio", "CreateAudioPipe",
perfetto::Track::FromPointer(this));
SendLogMessage("%s()", __func__);
base::UnsafeSharedMemoryRegion shared_memory_region =
reader_.TakeSharedMemoryRegion();
mojo::PlatformHandle socket_handle(foreign_socket_.Take());
if (!shared_memory_region.IsValid() || !socket_handle.is_valid()) {
std::move(created_callback).Run(nullptr);
OnError();
return;
}
std::move(created_callback)
.Run({std::in_place, std::move(shared_memory_region),
std::move(socket_handle)});
}
void OutputStream::OnControllerPlaying() {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
if (playing_)
return;
TRACE_EVENT_BEGIN("audio", "Playing", perfetto::Track::FromPointer(this));
playing_ = true;
if (observer_)
observer_->DidStartPlaying();
if (controller_.will_monitor_audio_levels()) {
const auto get_power_level = [](OutputStream* self) {
return self->controller_.ReadCurrentPowerAndClip().first;
};
audibility_helper_->StartPolling(
base::BindRepeating(std::move(get_power_level), base::Unretained(this)),
base::BindRepeating(&OutputStream::OnAudibleStateChanged,
base::Unretained(this)));
return;
}
if (observer_)
observer_->DidChangeAudibleState(true);
}
void OutputStream::OnControllerPaused() {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
if (!playing_)
return;
playing_ = false;
if (controller_.will_monitor_audio_levels()) {
audibility_helper_->StopPolling();
}
if (observer_)
observer_->DidStopPlaying();
TRACE_EVENT_END("audio", perfetto::Track::FromPointer(this));
}
void OutputStream::OnControllerError() {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
TRACE_EVENT_INSTANT("audio", "OnControllerError",
perfetto::Track::FromPointer(this));
SendLogMessage("%s()", __func__);
audibility_helper_->StopPolling();
if (log_)
log_->OnError();
if (observer_) {
observer_.ResetWithReason(
static_cast<uint32_t>(media::mojom::AudioOutputStreamObserver::
DisconnectReason::kPlatformError),
std::string());
}
OnError();
}
void OutputStream::OnLog(std::string_view message) {
if (log_) {
log_->OnLogMessage(base::StringPrintf("%s", std::string(message).c_str()));
}
}
void OutputStream::OnError() {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
TRACE_EVENT_INSTANT("audio", "OnError", perfetto::Track::FromPointer(this));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&OutputStream::CallDeleter, weak_factory_.GetWeakPtr()));
receiver_.reset();
}
void OutputStream::CallDeleter() {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
std::move(delete_callback_).Run(this);
}
void OutputStream::OnAudibleStateChanged(bool is_audible) {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
if (is_audible) {
TRACE_EVENT_BEGIN("audio", "Audible", perfetto::Track::FromPointer(this));
} else {
TRACE_EVENT_END("audio", perfetto::Track::FromPointer(this));
}
if (observer_) {
observer_->DidChangeAudibleState(is_audible);
}
}
void OutputStream::SendLogMessage(const char* format, ...) {
if (!log_)
return;
va_list args;
va_start(args, format);
#if BUILDFLAG(ARKWEB_MEDIA)
log_->OnLogMessage(
"audio::OS::" + base::StringPrintV(format, args) +
base::StringPrintf(" [controller_hash=%016x]",
base::FastHash(base::byte_span_from_ref(&controller_))));
#else
log_->OnLogMessage(
"audio::OS::" + base::StringPrintV(format, args) +
base::StringPrintf(" [controller=0x%" PRIXPTR "]",
reinterpret_cast<uintptr_t>(&controller_)));
#endif
va_end(args);
}
std::unique_ptr<OutputStream::AudibilityHelper>
OutputStream::MakeAudibilityHelperForTest() {
return std::make_unique<AudibilityHelperImpl>();
}
}