blob: a7824005b3d2989be2bcd6fffd3f4bc5b43d1a1b [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 "garnet/bin/media/audio_core/audio_device.h"
#include "garnet/bin/media/audio_core/audio_device_manager.h"
#include "garnet/bin/media/audio_core/audio_link.h"
#include "garnet/bin/media/audio_core/audio_output.h"
#include "lib/fxl/logging.h"
#include "lib/fxl/time/time_delta.h"
namespace media::audio {
namespace {
std::string AudioDeviceUniqueIdToString(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);
}
} // namespace
AudioDevice::AudioDevice(AudioObject::Type type, AudioDeviceManager* manager)
: AudioObject(type), manager_(manager), driver_(new AudioDriver(this)) {
FXL_DCHECK(manager_);
FXL_DCHECK((type == Type::Input) || (type == Type::Output));
}
AudioDevice::~AudioDevice() {
FXL_DCHECK(is_shutting_down());
FXL_DCHECK(!device_settings_ || !device_settings_->InContainer());
}
void AudioDevice::Wakeup() {
FXL_DCHECK(mix_wakeup_ != nullptr);
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) {
// 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()) {
fbl::AutoLock links_lock(&links_lock_);
for (auto& link : source_links_) {
if (link->GetSource()->type() == AudioObject::Type::AudioRenderer) {
link->bookkeeping()->gain.SetDestMute(
limited.flags & fuchsia::media::AudioGainInfoFlag_Mute);
link->bookkeeping()->gain.SetDestGain(limited.gain_db);
}
}
} else {
// For inputs, change the gain of all links where it is the source.
FXL_DCHECK(is_input());
fbl::AutoLock links_lock(&links_lock_);
for (auto& link : dest_links_) {
if (link->GetDest()->type() == AudioObject::Type::AudioCapturer) {
link->bookkeeping()->gain.SetSourceMute(
limited.flags & fuchsia::media::AudioGainInfoFlag_Mute);
link->bookkeeping()->gain.SetSourceGain(limited.gain_db);
}
}
}
FXL_DCHECK(device_settings_ != nullptr);
if (device_settings_->SetGainInfo(limited, set_flags)) {
Wakeup();
}
}
zx_status_t AudioDevice::Init() {
// TODO(johngro) : See MG-940. Eliminate this priority boost as soon as we
// have a more official way of meeting real-time latency requirements.
mix_domain_ = ::dispatcher::ExecutionDomain::Create(24);
mix_wakeup_ = ::dispatcher::WakeupEvent::Create();
if ((mix_domain_ == nullptr) || (mix_wakeup_ == nullptr)) {
return ZX_ERR_NO_MEMORY;
}
::dispatcher::WakeupEvent::ProcessHandler process_handler(
[output = fbl::WrapRefPtr(this)](
::dispatcher::WakeupEvent* event) -> zx_status_t {
OBTAIN_EXECUTION_DOMAIN_TOKEN(token, output->mix_domain_);
output->OnWakeup();
return ZX_OK;
});
zx_status_t res =
mix_wakeup_->Activate(mix_domain_, std::move(process_handler));
if (res != ZX_OK) {
FXL_LOG(ERROR) << "Failed to activate wakeup event for AudioDevice! "
<< "(res " << res << ")";
return res;
}
return ZX_OK;
}
void AudioDevice::Cleanup() {}
void 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.
FXL_DCHECK(device_settings_ == nullptr);
FXL_DCHECK(driver() != nullptr);
device_settings_ = AudioDeviceSettings::Create(*driver(), is_input());
// Now poke our manager.
FXL_DCHECK(manager_);
manager_->ScheduleMainThreadTask(
[manager = manager_, self = fbl::WrapRefPtr(this)]() {
manager->ActivateDevice(self);
});
}
}
void 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()) {
PreventNewLinks();
FXL_DCHECK(mix_domain_);
mix_domain_->DeactivateFromWithinDomain();
FXL_DCHECK(manager_);
manager_->ScheduleMainThreadTask(
[manager = manager_, self = fbl::WrapRefPtr(this)]() {
manager->RemoveDevice(self);
});
}
}
void AudioDevice::DeactivateDomain() {
if (mix_domain_ != nullptr) {
mix_domain_->Deactivate();
}
}
zx_status_t AudioDevice::Startup() {
// If our derived class failed to initialize, Just get out. We are being
// called by the output manager, and they will remove us from the set of
// active outputs as a result of us failing to initialize.
zx_status_t res = Init();
if (res != ZX_OK) {
DeactivateDomain();
return res;
}
// Poke the output once so it gets a chance to actually start running.
Wakeup();
return ZX_OK;
}
void AudioDevice::Shutdown() {
if (shut_down_) {
return;
}
// Make sure no new callbacks can be generated, and that pending callbacks
// have been nerfed.
DeactivateDomain();
// Unlink ourselves from everything we are currently attached to.
Unlink();
// Give our derived class a chance to clean up its resources.
Cleanup();
// We are now completely shut down. The only reason we have this flag is to
// make sure that Shutdown is idempotent.
shut_down_ = true;
}
bool AudioDevice::UpdatePlugState(bool plugged, zx_time_t plug_time) {
if ((plugged != plugged_) && (plug_time >= plug_time_)) {
plugged_ = plugged;
plug_time_ = plug_time;
return true;
}
return false;
}
const fbl::RefPtr<DriverRingBuffer>& AudioDevice::driver_ring_buffer() const {
return driver_->ring_buffer();
};
const TimelineFunction& AudioDevice::driver_clock_mono_to_ring_pos_bytes()
const {
return driver_->clock_mono_to_ring_pos_bytes();
};
void AudioDevice::GetDeviceInfo(
::fuchsia::media::AudioDeviceInfo* out_info) const {
const auto& drv = *driver();
out_info->name = drv.manufacturer_name() + ' ' + drv.product_name();
out_info->unique_id = AudioDeviceUniqueIdToString(drv.persistent_unique_id());
out_info->token_id = token();
out_info->is_input = is_input();
out_info->is_default = false;
FXL_DCHECK(device_settings_);
device_settings_->GetGainInfo(&out_info->gain_info);
}
} // namespace media::audio