blob: 90be8aa9ba68f49c5ddf460bd3b8d66b55df4b4c [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 <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);
}
AudioDevice::AudioDevice(AudioObject::Type type, ThreadingModel* threading_model,
DeviceRegistry* registry, LinkMatrix* link_matrix)
: AudioObject(type),
device_registry_(*registry),
threading_model_(*threading_model),
mix_domain_(threading_model->AcquireMixDomain()),
driver_(new AudioDriver(this)),
link_matrix_(*link_matrix) {
FX_DCHECK(registry);
FX_DCHECK((type == Type::Input) || (type == Type::Output));
FX_DCHECK(link_matrix);
EstablishReferenceClock();
}
void AudioDevice::EstablishReferenceClock() {
// This clock can be created at AudioDevice ctor time, but once we receive details from the driver
// (such as whether the device is in the monotonic clock domain), we may transition this to become
// a recovered clock that tracks the device's DMA progress across the ring buffer.
auto status =
zx::clock::create(ZX_CLOCK_OPT_MONOTONIC | ZX_CLOCK_OPT_CONTINUOUS | ZX_CLOCK_OPT_AUTO_START,
nullptr, &ref_clock_);
FX_DCHECK(status == ZX_OK);
}
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, uint32_t 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::AudioGainInfoFlag_Mute;
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::AudioGainInfoFlag_Mute;
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_) && !is_shutting_down()) {
plugged_ = plugged;
plug_time_ = plug_time;
return true;
}
return false;
}
const std::shared_ptr<RingBuffer>& AudioDevice::driver_ring_buffer() const {
return driver_->ring_buffer();
};
TimelineFunction AudioDevice::device_reference_clock_to_ring_pos_bytes() const {
return driver_->clock_mono_to_ring_pos_bytes();
};
void AudioDevice::GetDeviceInfo(fuchsia::media::AudioDeviceInfo* out_info) const {
TRACE_DURATION("audio", "AudioDevice::GetDeviceInfo");
const auto& drv = *driver();
out_info->name = drv.manufacturer_name() + ' ' + drv.product_name();
out_info->unique_id = UniqueIdToString(drv.persistent_unique_id());
out_info->token_id = token();
out_info->is_input = is_input();
out_info->is_default = false;
FX_DCHECK(device_settings_);
device_settings_->GetGainInfo(&out_info->gain_info);
}
} // namespace media::audio