blob: a06f859df971033e12ce336bc5770a05daea6b1f [file] [log] [blame]
// Copyright 2017 The Fuchsia 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 "src/media/audio/audio_core/audio_device.h"
#include <lib/fit/bridge.h>
#include <lib/trace/event.h>
#include "src/media/audio/audio_core/audio_device_manager.h"
#include "src/media/audio/audio_core/audio_driver.h"
#include "src/media/audio/audio_core/audio_output.h"
#include "src/media/audio/audio_core/utils.h"
namespace media::audio {
namespace {
constexpr float kDefaultDeviceGain = 0.;
} // namespace
// static
std::string AudioDevice::UniqueIdToString(const audio_stream_unique_id_t& id) {
static_assert(sizeof(id.data) == 16, "Unexpected unique ID size");
char buf[(sizeof(id.data) * 2) + 1];
const auto& d = id.data;
snprintf(buf, sizeof(buf), "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10], d[11], d[12], d[13],
d[14], d[15]);
return std::string(buf, sizeof(buf) - 1);
}
// static
fit::result<audio_stream_unique_id_t> AudioDevice::UniqueIdFromString(const std::string& id) {
if (id.size() != 32) {
return fit::error();
}
audio_stream_unique_id_t unique_id;
auto& d = unique_id.data;
const auto captured =
sscanf(id.c_str(),
"%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx",
&d[0], &d[1], &d[2], &d[3], &d[4], &d[5], &d[6], &d[7], &d[8], &d[9], &d[10], &d[11],
&d[12], &d[13], &d[14], &d[15]);
if (captured != 16) {
return fit::error();
}
return fit::ok(unique_id);
}
// Simple accessor here (not in .h) because of forward-declaration issues with AudioDriver
AudioClock& AudioDevice::reference_clock() {
FX_DCHECK(driver_);
return driver_->reference_clock();
}
const DeviceConfig::DeviceProfile& AudioDevice::profile() const {
if (is_output()) {
if (!driver_) {
return config_.default_output_device_profile();
}
const auto& device_id = driver_->persistent_unique_id();
return config_.output_device_profile(device_id);
} else {
if (!driver_) {
return config_.default_input_device_profile();
}
const auto& device_id = driver_->persistent_unique_id();
return config_.input_device_profile(device_id);
}
}
AudioDevice::AudioDevice(AudioObject::Type type, const std::string& name,
ThreadingModel* threading_model, DeviceRegistry* registry,
LinkMatrix* link_matrix, std::unique_ptr<AudioDriver> driver)
: AudioObject(type),
name_(name),
device_registry_(*registry),
threading_model_(*threading_model),
mix_domain_(threading_model->AcquireMixDomain(type == Type::Input ? "input-device"
: "output-device")),
driver_(std::move(driver)),
link_matrix_(*link_matrix) {
FX_DCHECK(registry);
FX_DCHECK((type == Type::Input) || (type == Type::Output));
FX_DCHECK(link_matrix);
}
AudioDevice::~AudioDevice() = default;
std::optional<Format> AudioDevice::format() const {
auto _driver = driver();
if (!_driver) {
return std::nullopt;
}
return _driver->GetFormat();
}
void AudioDevice::Wakeup() {
TRACE_DURATION("audio", "AudioDevice::Wakeup");
mix_wakeup_.Signal();
}
uint64_t AudioDevice::token() const {
return driver_ ? driver_->stream_channel_koid() : ZX_KOID_INVALID;
}
// Change a device's gain, propagating the change to the affected links.
void AudioDevice::SetGainInfo(const fuchsia::media::AudioGainInfo& info,
fuchsia::media::AudioGainValidFlags set_flags) {
TRACE_DURATION("audio", "AudioDevice::SetGainInfo");
// Limit the request to what the hardware can support
fuchsia::media::AudioGainInfo limited = info;
ApplyGainLimits(&limited, set_flags);
// For outputs, change the gain of all links where it is the destination.
if (is_output()) {
link_matrix_.ForEachSourceLink(*this, [&limited](LinkMatrix::LinkHandle link) {
if (link.object->type() == AudioObject::Type::AudioRenderer) {
const auto muted = (limited.flags & fuchsia::media::AudioGainInfoFlags::MUTE) ==
fuchsia::media::AudioGainInfoFlags::MUTE;
if (muted) {
FX_LOGS(WARNING) << "Destination device is muted";
} else {
// TODO(fxbug.dev/51049) Logging should be removed upon creation of inspect tool or other
// real-time method for gain observation
FX_LOGS(INFO) << "Destination device gain=" << limited.gain_db;
}
link.mixer->bookkeeping().gain.SetDestGain(muted ? fuchsia::media::audio::MUTED_GAIN_DB
: limited.gain_db);
}
});
} else {
// For inputs, change the gain of all links where it is the source.
FX_DCHECK(is_input());
link_matrix_.ForEachDestLink(*this, [&limited](LinkMatrix::LinkHandle link) {
if (link.object->type() == AudioObject::Type::AudioCapturer) {
const auto muted = (limited.flags & fuchsia::media::AudioGainInfoFlags::MUTE) ==
fuchsia::media::AudioGainInfoFlags::MUTE;
if (muted) {
FX_LOGS(WARNING) << "Source device is muted";
} else {
// TODO(fxbug.dev/51049) Logging should be removed upon creation of inspect tool or other
// real-time method for gain observation
FX_LOGS(INFO) << "Source device gain=" << limited.gain_db;
}
link.mixer->bookkeeping().gain.SetSourceGain(muted ? fuchsia::media::audio::MUTED_GAIN_DB
: limited.gain_db);
}
});
}
FX_DCHECK(device_settings_ != nullptr);
if (device_settings_->SetGainInfo(limited, set_flags)) {
Wakeup();
}
}
zx_status_t AudioDevice::Init() {
TRACE_DURATION("audio", "AudioDevice::Init");
WakeupEvent::ProcessHandler process_handler(
[weak_output = weak_from_this()](WakeupEvent* event) -> zx_status_t {
auto output = weak_output.lock();
if (output) {
OBTAIN_EXECUTION_DOMAIN_TOKEN(token, &output->mix_domain());
output->OnWakeup();
}
return ZX_OK;
});
zx_status_t res = mix_wakeup_.Activate(mix_domain_->dispatcher(), std::move(process_handler));
if (res != ZX_OK) {
FX_PLOGS(ERROR, res) << "Failed to activate wakeup event for AudioDevice";
return res;
}
return ZX_OK;
}
void AudioDevice::Cleanup() {
TRACE_DURATION("audio", "AudioDevice::Cleanup");
mix_wakeup_.Deactivate();
// ThrottleOutput devices have no driver, so check for that.
if (driver_ != nullptr) {
// Instruct the driver to release all its resources (channels, timer).
driver_->Cleanup();
}
mix_domain_ = nullptr;
}
void AudioDevice::ActivateSelf() {
TRACE_DURATION("audio", "AudioDevice::ActivateSelf");
// If we aren't shutting down, tell DeviceManager we are ready for work.
if (!is_shutting_down()) {
// Create default settings. The device manager will restore these settings
// from persistent storage for us when it gets our activation message.
FX_DCHECK(device_settings_ == nullptr);
FX_DCHECK(driver() != nullptr);
HwGainState gain_state = driver()->hw_gain_state();
// We disregard the device's gain at the time of connection and set it to 0,
// pending restoration of device_settings.
gain_state.cur_gain = kDefaultDeviceGain;
const auto id = driver()->persistent_unique_id();
device_settings_ = fbl::MakeRefCounted<AudioDeviceSettings>(id, gain_state, is_input());
// Now poke our manager.
threading_model().FidlDomain().PostTask(
[self = shared_from_this()]() { self->device_registry().ActivateDevice(std::move(self)); });
}
}
void AudioDevice::ShutdownSelf() {
TRACE_DURATION("audio", "AudioDevice::ShutdownSelf");
// If we are not already in the process of shutting down, send a message to
// the main message loop telling it to complete the shutdown process.
if (!is_shutting_down()) {
shutting_down_.store(true);
threading_model().FidlDomain().PostTask(
[self = shared_from_this()]() { self->device_registry().RemoveDevice(self); });
}
}
fit::promise<void, zx_status_t> AudioDevice::Startup() {
TRACE_DURATION("audio", "AudioDevice::Startup");
fit::bridge<void, zx_status_t> bridge;
mix_domain_->PostTask(
[self = shared_from_this(), completer = std::move(bridge.completer)]() mutable {
OBTAIN_EXECUTION_DOMAIN_TOKEN(token, &self->mix_domain());
zx_status_t res = self->Init();
if (res != ZX_OK) {
self->Cleanup();
completer.complete_error(res);
return;
}
self->OnWakeup();
completer.complete_ok();
});
return bridge.consumer.promise();
}
fit::promise<void> AudioDevice::Shutdown() {
TRACE_DURATION("audio", "AudioDevice::Shutdown");
// The only reason we have this flag is to make sure that Shutdown is idempotent.
if (shut_down_) {
return fit::make_ok_promise();
}
shut_down_ = true;
// Give our derived class, and our driver, a chance to clean up resources.
fit::bridge<void> bridge;
mix_domain_->PostTask(
[self = shared_from_this(), completer = std::move(bridge.completer)]() mutable {
OBTAIN_EXECUTION_DOMAIN_TOKEN(token, &self->mix_domain());
self->Cleanup();
completer.complete_ok();
});
return bridge.consumer.promise();
}
bool AudioDevice::UpdatePlugState(bool plugged, zx::time plug_time) {
TRACE_DURATION("audio", "AudioDevice::UpdatePlugState");
if ((plugged != plugged_) && (plug_time >= plug_time_)) {
plugged_ = plugged;
plug_time_ = plug_time;
return true;
}
return false;
}
void AudioDevice::UpdateRoutableState(bool routable) {
TRACE_INSTANT("audio", "AudioDevice::UpdateRoutableState", TRACE_SCOPE_PROCESS, "Routable",
routable);
routable_ = routable;
}
const std::shared_ptr<ReadableRingBuffer>& AudioDevice::driver_readable_ring_buffer() const {
return driver_->readable_ring_buffer();
};
const std::shared_ptr<WritableRingBuffer>& AudioDevice::driver_writable_ring_buffer() const {
return driver_->writable_ring_buffer();
};
const TimelineFunction& AudioDevice::driver_ref_time_to_frac_presentation_frame() const {
return driver()->ref_time_to_frac_presentation_frame();
}
const TimelineFunction& AudioDevice::driver_ref_time_to_frac_safe_read_or_write_frame() const {
return driver()->ref_time_to_frac_safe_read_or_write_frame();
}
fuchsia::media::AudioDeviceInfo AudioDevice::GetDeviceInfo() const {
TRACE_DURATION("audio", "AudioDevice::GetDeviceInfo");
FX_DCHECK(device_settings_);
return {
.name = driver()->manufacturer_name() + ' ' + driver()->product_name(),
.unique_id = UniqueIdToString(driver()->persistent_unique_id()),
.token_id = token(),
.is_input = is_input(),
.gain_info = device_settings_->GetGainInfo(),
.is_default = false,
};
}
} // namespace media::audio